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.
This commit is contained in:
parent
fcc0b906e5
commit
a69391b1d8
63
Fbrowser.py
63
Fbrowser.py
@ -1,11 +1,13 @@
|
|||||||
"""File browser implementation with tools and audio playback.
|
"""File browser implementation with tools and audio playback.
|
||||||
this includes a midi player"""
|
this includes a midi player"""
|
||||||
|
|
||||||
# flake8: noqa: E501
|
# flake8: noqa: E501
|
||||||
# This is the main file this file is the one to run.
|
# This is the main file this file is the one to run.
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import warnings
|
import warnings
|
||||||
|
import argparse
|
||||||
from PyQt6.QtWidgets import (
|
from PyQt6.QtWidgets import (
|
||||||
QApplication,
|
QApplication,
|
||||||
QMainWindow,
|
QMainWindow,
|
||||||
@ -38,10 +40,21 @@ from ScanOrg101 import (
|
|||||||
ArchiveExtractor,
|
ArchiveExtractor,
|
||||||
)
|
)
|
||||||
import mutagen
|
import mutagen
|
||||||
|
from logging_setup import setup_logging
|
||||||
|
|
||||||
warnings.filterwarnings("ignore", category=DeprecationWarning)
|
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):
|
class MetadataTooltip(QFrame):
|
||||||
"""Custom styled tooltip for displaying audio metadata"""
|
"""Custom styled tooltip for displaying audio metadata"""
|
||||||
|
|
||||||
@ -110,8 +123,10 @@ class MetadataTooltip(QFrame):
|
|||||||
class Fbrowser(QMainWindow):
|
class Fbrowser(QMainWindow):
|
||||||
"""Fbrowser main class"""
|
"""Fbrowser main class"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, logger):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
self.logger = logger
|
||||||
|
self.logger.info("Initializing Fbrowser application")
|
||||||
self.midplay = None
|
self.midplay = None
|
||||||
self.current_path = QDir.homePath()
|
self.current_path = QDir.homePath()
|
||||||
self.organizer = Organizer()
|
self.organizer = Organizer()
|
||||||
@ -129,9 +144,11 @@ class Fbrowser(QMainWindow):
|
|||||||
self.hover_position = None
|
self.hover_position = None
|
||||||
self.hover_path = None
|
self.hover_path = None
|
||||||
self.metadata_cache = {}
|
self.metadata_cache = {}
|
||||||
|
self.logger.debug("Fbrowser initialization complete")
|
||||||
|
|
||||||
def init_ui(self):
|
def init_ui(self):
|
||||||
"""initilize UI"""
|
"""initilize UI"""
|
||||||
|
self.logger.debug("Initializing UI components")
|
||||||
self.setWindowTitle("File Browser")
|
self.setWindowTitle("File Browser")
|
||||||
central_widget = QWidget()
|
central_widget = QWidget()
|
||||||
layout = QVBoxLayout(central_widget)
|
layout = QVBoxLayout(central_widget)
|
||||||
@ -266,13 +283,16 @@ class Fbrowser(QMainWindow):
|
|||||||
self.folder_contents_view.setMouseTracking(True)
|
self.folder_contents_view.setMouseTracking(True)
|
||||||
self.folder_contents_view.viewport().setMouseTracking(True) # type: ignore
|
self.folder_contents_view.viewport().setMouseTracking(True) # type: ignore
|
||||||
self.folder_contents_view.viewport().installEventFilter(self) # type: ignore
|
self.folder_contents_view.viewport().installEventFilter(self) # type: ignore
|
||||||
|
self.logger.debug("UI initialization complete")
|
||||||
|
|
||||||
def set_volume(self, volume):
|
def set_volume(self, volume):
|
||||||
"""Set Volume"""
|
"""Set Volume"""
|
||||||
self.audio_output.setVolume(volume / 100.0)
|
self.audio_output.setVolume(volume / 100.0)
|
||||||
|
self.logger.debug(f"Volume set to {volume}%")
|
||||||
|
|
||||||
def show_exit_popup(self):
|
def show_exit_popup(self):
|
||||||
"""Exit Prompt"""
|
"""Exit Prompt"""
|
||||||
|
self.logger.debug("Exit prompt displayed")
|
||||||
reply = QMessageBox.question(
|
reply = QMessageBox.question(
|
||||||
self,
|
self,
|
||||||
"Exit",
|
"Exit",
|
||||||
@ -281,20 +301,24 @@ class Fbrowser(QMainWindow):
|
|||||||
QMessageBox.StandardButton.No,
|
QMessageBox.StandardButton.No,
|
||||||
)
|
)
|
||||||
if reply == QMessageBox.StandardButton.Yes:
|
if reply == QMessageBox.StandardButton.Yes:
|
||||||
|
self.logger.info("User confirmed exit, shutting down application")
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
def navigate_to_address(self):
|
def navigate_to_address(self):
|
||||||
"""Navigation Via Address Bar"""
|
"""Navigation Via Address Bar"""
|
||||||
new_path = self.address_bar.text()
|
new_path = self.address_bar.text()
|
||||||
|
self.logger.debug(f"Navigating to address: {new_path}")
|
||||||
if os.path.isdir(new_path):
|
if os.path.isdir(new_path):
|
||||||
self.add_to_history(self.current_path)
|
self.add_to_history(self.current_path)
|
||||||
self.current_path = new_path
|
self.current_path = new_path
|
||||||
self.update_file_views(new_path)
|
self.update_file_views(new_path)
|
||||||
else:
|
else:
|
||||||
|
self.logger.warning(f"Invalid directory path: {new_path}")
|
||||||
self.address_bar.setText(self.current_path)
|
self.address_bar.setText(self.current_path)
|
||||||
|
|
||||||
def add_to_history(self, path):
|
def add_to_history(self, path):
|
||||||
"""History"""
|
"""History"""
|
||||||
|
self.logger.debug(f"Adding path to history: {path}")
|
||||||
# If we're not at the end of the history, truncate it
|
# If we're not at the end of the history, truncate it
|
||||||
if self.history_position < len(self.history) - 1:
|
if self.history_position < len(self.history) - 1:
|
||||||
self.history = self.history[: self.history_position + 1]
|
self.history = self.history[: self.history_position + 1]
|
||||||
@ -307,12 +331,14 @@ class Fbrowser(QMainWindow):
|
|||||||
index = self.directory_model.mapToSource(index)
|
index = self.directory_model.mapToSource(index)
|
||||||
try:
|
try:
|
||||||
file_path = self.tree_model.filePath(index)
|
file_path = self.tree_model.filePath(index)
|
||||||
|
self.logger.debug(f"Changing directory to: {file_path}")
|
||||||
if os.path.isdir(file_path):
|
if os.path.isdir(file_path):
|
||||||
self.add_to_history(self.current_path)
|
self.add_to_history(self.current_path)
|
||||||
self.current_path = file_path
|
self.current_path = file_path
|
||||||
self.update_file_views(file_path)
|
self.update_file_views(file_path)
|
||||||
except (OSError, IOError) as e:
|
except (OSError, IOError) as e:
|
||||||
print(f"Error Changing Dirs.: {e}")
|
self.logger.error(f"Error changing directory: {e}")
|
||||||
|
logger.debug(f"Error Changing Dirs.: {e}")
|
||||||
|
|
||||||
def update_file_views(self, path):
|
def update_file_views(self, path):
|
||||||
"""Updates the File trees"""
|
"""Updates the File trees"""
|
||||||
@ -726,7 +752,7 @@ class Fbrowser(QMainWindow):
|
|||||||
self.show_metadata_tooltip(metadata)
|
self.show_metadata_tooltip(metadata)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error extracting metadata for hover: {e}")
|
logger.debug(f"Error extracting metadata for hover: {e}")
|
||||||
|
|
||||||
def _get_tag(self, audio, tag_name, default_value):
|
def _get_tag(self, audio, tag_name, default_value):
|
||||||
"""Helper method to safely extract tags from audio files"""
|
"""Helper method to safely extract tags from audio files"""
|
||||||
@ -762,6 +788,7 @@ class Fbrowser(QMainWindow):
|
|||||||
|
|
||||||
def closeEvent(self, event):
|
def closeEvent(self, event):
|
||||||
"""Handle application close event"""
|
"""Handle application close event"""
|
||||||
|
self.logger.info("Application closing")
|
||||||
# Close database connection
|
# Close database connection
|
||||||
if hasattr(self, "organizer") and self.organizer:
|
if hasattr(self, "organizer") and self.organizer:
|
||||||
self.organizer.close()
|
self.organizer.close()
|
||||||
@ -774,7 +801,7 @@ class Fbrowser(QMainWindow):
|
|||||||
if not self.should_auto_scan(path):
|
if not self.should_auto_scan(path):
|
||||||
return
|
return
|
||||||
|
|
||||||
print(f"Auto-scanning directory: {path}")
|
self.logger.info(f"Auto-scanning directory: {path}")
|
||||||
|
|
||||||
# Initialize organizer with database connection if not already done
|
# Initialize organizer with database connection if not already done
|
||||||
if not hasattr(self.organizer, "db") or self.organizer.db is None:
|
if not hasattr(self.organizer, "db") or self.organizer.db is None:
|
||||||
@ -797,13 +824,13 @@ class Fbrowser(QMainWindow):
|
|||||||
files_to_process.append(file_path)
|
files_to_process.append(file_path)
|
||||||
|
|
||||||
if files_to_process:
|
if files_to_process:
|
||||||
print(
|
self.logger.debug(
|
||||||
f"Found {len(files_to_process)} files without metadata, extracting..."
|
f"Found {len(files_to_process)} files without metadata, extracting..."
|
||||||
)
|
)
|
||||||
self.organizer.file_list = files_to_process
|
self.organizer.file_list = files_to_process
|
||||||
self.organizer.extract_metadata()
|
self.organizer.extract_metadata()
|
||||||
else:
|
else:
|
||||||
print(
|
self.logger.debug(
|
||||||
"All files already have metadata in database, skipping extraction"
|
"All files already have metadata in database, skipping extraction"
|
||||||
)
|
)
|
||||||
self.progress_bar.setValue(100)
|
self.progress_bar.setValue(100)
|
||||||
@ -817,16 +844,16 @@ class Fbrowser(QMainWindow):
|
|||||||
def auto_extract_metadata(self):
|
def auto_extract_metadata(self):
|
||||||
"""Automatically extract metadata after scanning"""
|
"""Automatically extract metadata after scanning"""
|
||||||
if not self.organizer.file_list:
|
if not self.organizer.file_list:
|
||||||
print("No audio files found to extract metadata from")
|
logger.debug("No audio files found to extract metadata from")
|
||||||
return
|
return
|
||||||
|
|
||||||
print(
|
self.logger.debug(
|
||||||
f"Auto-extracting metadata for {len(self.organizer.file_list)} files"
|
f"Auto-extracting metadata for {len(self.organizer.file_list)} files"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Connect signals
|
# Connect signals
|
||||||
self.organizer.on_progress_update = self.update_progress # type: ignore
|
self.organizer.on_progress_update = self.update_progress # type: ignore
|
||||||
self.organizer.on_metadata_complete = lambda: print("Metadata extraction complete") # type: ignore
|
self.organizer.on_metadata_complete = lambda: logger.debug("Metadata extraction complete") # type: ignore
|
||||||
|
|
||||||
# Start extraction
|
# Start extraction
|
||||||
self.organizer.extract_metadata()
|
self.organizer.extract_metadata()
|
||||||
@ -859,8 +886,22 @@ class Fbrowser(QMainWindow):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
def main():
|
||||||
|
# Parse command line arguments
|
||||||
|
args = parse_arguments()
|
||||||
|
|
||||||
|
# Setup logging
|
||||||
|
logger = setup_logging(args)
|
||||||
|
|
||||||
|
logger.info("Starting Fbrowser application")
|
||||||
|
|
||||||
app = QApplication(sys.argv)
|
app = QApplication(sys.argv)
|
||||||
ex = Fbrowser()
|
ex = Fbrowser(logger)
|
||||||
ex.show()
|
ex.show()
|
||||||
|
|
||||||
|
logger.info("Application UI displayed")
|
||||||
sys.exit(app.exec())
|
sys.exit(app.exec())
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|||||||
@ -5,6 +5,7 @@ ScanOrg101.py - Enhanced file scanning and organization module
|
|||||||
# flake8: noqa: E501
|
# flake8: noqa: E501
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import logging
|
||||||
import concurrent.futures
|
import concurrent.futures
|
||||||
from collections import deque
|
from collections import deque
|
||||||
import time
|
import time
|
||||||
@ -13,6 +14,9 @@ from dbman import FireflyDB
|
|||||||
from metaextract import MetadataExtractor, mutagen
|
from metaextract import MetadataExtractor, mutagen
|
||||||
from archiver import ArchiveExtractor
|
from archiver import ArchiveExtractor
|
||||||
|
|
||||||
|
# Get the logger
|
||||||
|
logger = logging.getLogger("fbroswer")
|
||||||
|
|
||||||
|
|
||||||
# Directory Filter Proxy Model
|
# Directory Filter Proxy Model
|
||||||
class DirectoryFilterProxyModel(QSortFilterProxyModel):
|
class DirectoryFilterProxyModel(QSortFilterProxyModel):
|
||||||
@ -20,6 +24,7 @@ class DirectoryFilterProxyModel(QSortFilterProxyModel):
|
|||||||
super().__init__()
|
super().__init__()
|
||||||
self.setFilterCaseSensitivity(Qt.CaseSensitivity.CaseInsensitive)
|
self.setFilterCaseSensitivity(Qt.CaseSensitivity.CaseInsensitive)
|
||||||
self.setFilterKeyColumn(0)
|
self.setFilterKeyColumn(0)
|
||||||
|
logger.debug("DirectoryFilterProxyModel initialized")
|
||||||
|
|
||||||
def filterAcceptsRow(self, source_row, source_parent):
|
def filterAcceptsRow(self, source_row, source_parent):
|
||||||
source_model = self.sourceModel()
|
source_model = self.sourceModel()
|
||||||
@ -57,6 +62,9 @@ class FileFilterProxyModel(QSortFilterProxyModel):
|
|||||||
".7z",
|
".7z",
|
||||||
".rar",
|
".rar",
|
||||||
]
|
]
|
||||||
|
logger.debug(
|
||||||
|
"FileFilterProxyModel initialized with allowed extensions"
|
||||||
|
)
|
||||||
|
|
||||||
def filterAcceptsRow(self, source_row, source_parent):
|
def filterAcceptsRow(self, source_row, source_parent):
|
||||||
source_model = self.sourceModel()
|
source_model = self.sourceModel()
|
||||||
@ -221,7 +229,9 @@ class FileScanner(QThread):
|
|||||||
self.progress_update.emit(progress)
|
self.progress_update.emit(progress)
|
||||||
last_update_time = current_time
|
last_update_time = current_time
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error scanning directory {dir_path}: {e}")
|
logger.debug(
|
||||||
|
f"Error scanning directory {dir_path}: {e}"
|
||||||
|
)
|
||||||
|
|
||||||
# Emit any remaining items in the final batch
|
# Emit any remaining items in the final batch
|
||||||
if batch and not self.stop_requested:
|
if batch and not self.stop_requested:
|
||||||
@ -268,10 +278,10 @@ class FileScanner(QThread):
|
|||||||
self.progress_update.emit(100) # Show complete for this directory
|
self.progress_update.emit(100) # Show complete for this directory
|
||||||
return items
|
return items
|
||||||
except PermissionError:
|
except PermissionError:
|
||||||
print(f"Permission denied: {path}")
|
logger.debug(f"Permission denied: {path}")
|
||||||
return []
|
return []
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
print(f"Error accessing {path}: {e}")
|
logger.debug(f"Error accessing {path}: {e}")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
def scan_single_directory_helper(self, path):
|
def scan_single_directory_helper(self, path):
|
||||||
@ -293,7 +303,7 @@ class FileScanner(QThread):
|
|||||||
):
|
):
|
||||||
items.append((entry.path, False))
|
items.append((entry.path, False))
|
||||||
except (PermissionError, OSError) as e:
|
except (PermissionError, OSError) as e:
|
||||||
print(f"Error accessing {path}: {e}")
|
logger.debug(f"Error accessing {path}: {e}")
|
||||||
|
|
||||||
return items, subdirs
|
return items, subdirs
|
||||||
|
|
||||||
@ -372,7 +382,7 @@ class Organizer:
|
|||||||
|
|
||||||
def scan_finished(self):
|
def scan_finished(self):
|
||||||
"""Handle scan completion"""
|
"""Handle scan completion"""
|
||||||
print(
|
logger.debug(
|
||||||
f"Scan complete. Found {len(self.dir_list)} directories\
|
f"Scan complete. Found {len(self.dir_list)} directories\
|
||||||
and {len(self.file_list)} files."
|
and {len(self.file_list)} files."
|
||||||
)
|
)
|
||||||
@ -388,13 +398,13 @@ class Organizer:
|
|||||||
def extract_metadata(self):
|
def extract_metadata(self):
|
||||||
"""Extract metadata from audio files using MetadataExtractor from metaextract.py"""
|
"""Extract metadata from audio files using MetadataExtractor from metaextract.py"""
|
||||||
if not self.file_list:
|
if not self.file_list:
|
||||||
print("No files to extract metadata from")
|
logger.debug("No files to extract metadata from")
|
||||||
return
|
return
|
||||||
|
|
||||||
# Verify database connection if enabled
|
# Verify database connection if enabled
|
||||||
if self.use_db and self.db:
|
if self.use_db and self.db:
|
||||||
if not self.db_manager.verify_database_connection():
|
if not self.db_manager.verify_database_connection():
|
||||||
print(
|
logger.debug(
|
||||||
"Warning: Database verification failed, continuing without database"
|
"Warning: Database verification failed, continuing without database"
|
||||||
)
|
)
|
||||||
self.use_db = False
|
self.use_db = False
|
||||||
@ -451,7 +461,7 @@ class Organizer:
|
|||||||
|
|
||||||
def metadata_extraction_complete(self):
|
def metadata_extraction_complete(self):
|
||||||
"""Handle metadata extraction completion"""
|
"""Handle metadata extraction completion"""
|
||||||
print(
|
logger.debug(
|
||||||
f"Metadata extraction complete. Artists: {len(getattr(self, 'artists', []))}, "
|
f"Metadata extraction complete. Artists: {len(getattr(self, 'artists', []))}, "
|
||||||
f"Albums: {len(getattr(self, 'albums', []))}, Genres: {len(getattr(self, 'genres', []))}, "
|
f"Albums: {len(getattr(self, 'albums', []))}, Genres: {len(getattr(self, 'genres', []))}, "
|
||||||
f"Years: {len(getattr(self, 'years', []))}"
|
f"Years: {len(getattr(self, 'years', []))}"
|
||||||
@ -460,16 +470,16 @@ class Organizer:
|
|||||||
def extract_archive(self, archive_path, extraction_dir):
|
def extract_archive(self, archive_path, extraction_dir):
|
||||||
"""Extract an archive to specified directory using ArchiveExtractor from archiver.py"""
|
"""Extract an archive to specified directory using ArchiveExtractor from archiver.py"""
|
||||||
if not os.path.isfile(archive_path):
|
if not os.path.isfile(archive_path):
|
||||||
print(f"Error: Archive file {archive_path} does not exist")
|
logger.debug(f"Error: Archive file {archive_path} does not exist")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if not os.path.isdir(extraction_dir):
|
if not os.path.isdir(extraction_dir):
|
||||||
print(
|
logger.debug(
|
||||||
f"Error: Extraction directory {extraction_dir} does not exist"
|
f"Error: Extraction directory {extraction_dir} does not exist"
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
print(f"Extracting archive {archive_path} to {extraction_dir}")
|
logger.debug(f"Extracting archive {archive_path} to {extraction_dir}")
|
||||||
|
|
||||||
# Create an ArchiveExtractor instance from archiver.py
|
# Create an ArchiveExtractor instance from archiver.py
|
||||||
self.archive_extractor = ArchiveExtractor(archive_path, extraction_dir)
|
self.archive_extractor = ArchiveExtractor(archive_path, extraction_dir)
|
||||||
@ -482,7 +492,7 @@ class Organizer:
|
|||||||
|
|
||||||
# Define completion handler
|
# Define completion handler
|
||||||
def on_extraction_complete(extracted_files):
|
def on_extraction_complete(extracted_files):
|
||||||
print(
|
logger.debug(
|
||||||
f"Archive extraction complete. Extracted {len(extracted_files)} files."
|
f"Archive extraction complete. Extracted {len(extracted_files)} files."
|
||||||
)
|
)
|
||||||
# Add extracted files to our file list if they match our criteria
|
# Add extracted files to our file list if they match our criteria
|
||||||
@ -520,7 +530,7 @@ class Organizer:
|
|||||||
]
|
]
|
||||||
|
|
||||||
if audio_files and self.use_db:
|
if audio_files and self.use_db:
|
||||||
print(
|
logger.debug(
|
||||||
f"Found {len(audio_files)} audio files in archive, extracting metadata..."
|
f"Found {len(audio_files)} audio files in archive, extracting metadata..."
|
||||||
)
|
)
|
||||||
temp_extractor = MetadataExtractor(audio_files)
|
temp_extractor = MetadataExtractor(audio_files)
|
||||||
@ -536,7 +546,7 @@ class Organizer:
|
|||||||
|
|
||||||
# Define error handler
|
# Define error handler
|
||||||
def on_extraction_error(error_message):
|
def on_extraction_error(error_message):
|
||||||
print(f"Archive extraction error: {error_message}")
|
logger.debug(f"Archive extraction error: {error_message}")
|
||||||
|
|
||||||
# Connect error signal
|
# Connect error signal
|
||||||
self.archive_extractor.extraction_error.connect(on_extraction_error)
|
self.archive_extractor.extraction_error.connect(on_extraction_error)
|
||||||
@ -550,7 +560,7 @@ class Organizer:
|
|||||||
):
|
):
|
||||||
"""Extract an archive to a specified directory or to a subdirectory in the same location"""
|
"""Extract an archive to a specified directory or to a subdirectory in the same location"""
|
||||||
if not os.path.isfile(archive_path):
|
if not os.path.isfile(archive_path):
|
||||||
print(f"Error: Archive file {archive_path} does not exist")
|
logger.debug(f"Error: Archive file {archive_path} does not exist")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# If no target directory is specified, create one based on the archive name
|
# If no target directory is specified, create one based on the archive name
|
||||||
@ -564,9 +574,11 @@ class Organizer:
|
|||||||
if not os.path.exists(target_directory):
|
if not os.path.exists(target_directory):
|
||||||
try:
|
try:
|
||||||
os.makedirs(target_directory)
|
os.makedirs(target_directory)
|
||||||
print(f"Created directory {target_directory}")
|
logger.debug(f"Created directory {target_directory}")
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
print(f"Error creating directory {target_directory}: {e}")
|
logger.debug(
|
||||||
|
f"Error creating directory {target_directory}: {e}"
|
||||||
|
)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return self.extract_archive(archive_path, target_directory)
|
return self.extract_archive(archive_path, target_directory)
|
||||||
@ -580,7 +592,7 @@ class Organizer:
|
|||||||
# Delegate to the db_manager
|
# Delegate to the db_manager
|
||||||
return self.db_manager.has_metadata_in_db(file_path)
|
return self.db_manager.has_metadata_in_db(file_path)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error checking metadata in database: {e}")
|
logger.debug(f"Error checking metadata in database: {e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def get_metadata_from_db(self, file_path):
|
def get_metadata_from_db(self, file_path):
|
||||||
@ -592,20 +604,20 @@ class Organizer:
|
|||||||
# Delegate to the db_manager
|
# Delegate to the db_manager
|
||||||
return self.db_manager.get_metadata_from_db(file_path)
|
return self.db_manager.get_metadata_from_db(file_path)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error retrieving metadata from database: {e}")
|
logger.debug(f"Error retrieving metadata from database: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def store_metadata(self, metadata):
|
def store_metadata(self, metadata):
|
||||||
"""Store audio file metadata in the database"""
|
"""Store audio file metadata in the database"""
|
||||||
if not self.use_db or not self.db:
|
if not self.use_db or not self.db:
|
||||||
print("Database usage is disabled, not storing metadata")
|
logger.debug("Database usage is disabled, not storing metadata")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Delegate to the db_manager
|
# Delegate to the db_manager
|
||||||
return self.db_manager.store_metadata(metadata)
|
return self.db_manager.store_metadata(metadata)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error storing metadata in database: {e}")
|
logger.debug(f"Error storing metadata in database: {e}")
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import os
|
import os
|
||||||
|
import logging
|
||||||
import zipfile
|
import zipfile
|
||||||
import py7zr
|
import py7zr
|
||||||
import rarfile # typed: ignore
|
import rarfile # typed: ignore
|
||||||
@ -6,6 +7,10 @@ import rarfile # typed: ignore
|
|||||||
from PyQt6.QtCore import QThread, pyqtSignal
|
from PyQt6.QtCore import QThread, pyqtSignal
|
||||||
|
|
||||||
|
|
||||||
|
# Get the logger
|
||||||
|
logger = logging.getLogger("fbroswer")
|
||||||
|
|
||||||
|
|
||||||
# Archive Extractor not fully tested or implemented
|
# Archive Extractor not fully tested or implemented
|
||||||
class ArchiveExtractor(QThread):
|
class ArchiveExtractor(QThread):
|
||||||
extraction_progress = pyqtSignal(int)
|
extraction_progress = pyqtSignal(int)
|
||||||
@ -116,7 +121,7 @@ class ArchiveExtractor(QThread):
|
|||||||
def extract_archives(self):
|
def extract_archives(self):
|
||||||
"""Extract archives"""
|
"""Extract archives"""
|
||||||
if not self.file_list:
|
if not self.file_list:
|
||||||
print("No files to extract archives from")
|
logger.debug("No files to extract archives from")
|
||||||
return
|
return
|
||||||
|
|
||||||
self.archive_extractor = ArchiveExtractor(
|
self.archive_extractor = ArchiveExtractor(
|
||||||
@ -136,7 +141,7 @@ class ArchiveExtractor(QThread):
|
|||||||
|
|
||||||
def archive_extraction_complete(self):
|
def archive_extraction_complete(self):
|
||||||
"""Handle archive extraction completion"""
|
"""Handle archive extraction completion"""
|
||||||
print("Archive extraction complete.")
|
logger.debug("Archive extraction complete.")
|
||||||
|
|
||||||
if self.on_progress_update:
|
if self.on_progress_update:
|
||||||
self.on_progress_update(100)
|
self.on_progress_update(100)
|
||||||
|
|||||||
121
dbman.py
121
dbman.py
@ -1,6 +1,10 @@
|
|||||||
|
import logging
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from ifireflylib import IFireflyClient as FireflyDatabase
|
from ifireflylib import IFireflyClient as FireflyDatabase
|
||||||
|
|
||||||
|
# Get the logger
|
||||||
|
logger = logging.getLogger("fbroswer")
|
||||||
|
|
||||||
|
|
||||||
class FireflyDB:
|
class FireflyDB:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@ -11,12 +15,14 @@ class FireflyDB:
|
|||||||
self.albums = set()
|
self.albums = set()
|
||||||
self.genres = set()
|
self.genres = set()
|
||||||
self.years = set()
|
self.years = set()
|
||||||
|
logger.debug("FireflyDB instance initialized")
|
||||||
|
|
||||||
def connect_to(
|
def connect_to(
|
||||||
self, use_db=False, db_host="localhost", db_port=6379, db_password=None
|
self, use_db=False, db_host="localhost", db_port=6379, db_password=None
|
||||||
):
|
):
|
||||||
# Set the use_db flag
|
# Set the use_db flag
|
||||||
self.use_db = use_db
|
self.use_db = use_db
|
||||||
|
logger.info(f"Database usage set to: {use_db}")
|
||||||
|
|
||||||
# Metadata organization
|
# Metadata organization
|
||||||
self.artists = set()
|
self.artists = set()
|
||||||
@ -26,26 +32,30 @@ class FireflyDB:
|
|||||||
|
|
||||||
if use_db:
|
if use_db:
|
||||||
try:
|
try:
|
||||||
print(f"Connecting to FireflyDB at {db_host}:{db_port}")
|
logger.info(f"Connecting to FireflyDB at {db_host}:{db_port}")
|
||||||
self.db = FireflyDatabase(
|
self.db = FireflyDatabase(
|
||||||
host=db_host, port=db_port, password=db_password
|
host=db_host, port=db_port, password=db_password
|
||||||
)
|
)
|
||||||
# Test connection
|
# Test connection
|
||||||
if not self.db.ping():
|
if not self.db.ping():
|
||||||
print(
|
logger.warning(
|
||||||
"Warning: Could not connect to FireflyDB. Continuing without database."
|
"Could not connect to FireflyDB. Continuing without database."
|
||||||
)
|
)
|
||||||
self.use_db = False
|
self.use_db = False
|
||||||
self.db = None
|
self.db = None
|
||||||
else:
|
else:
|
||||||
print("Successfully connected to FireflyDB")
|
logger.info("Successfully connected to FireflyDB")
|
||||||
# Store connection timestamp
|
# Store connection timestamp
|
||||||
timestamp = str(datetime.now())
|
timestamp = str(datetime.now())
|
||||||
print(f"Setting last_connection timestamp: {timestamp}")
|
logger.debug(
|
||||||
|
f"Setting last_connection timestamp: {timestamp}"
|
||||||
|
)
|
||||||
self.db.string_ops.string_set("last_connection", timestamp)
|
self.db.string_ops.string_set("last_connection", timestamp)
|
||||||
print("Connection timestamp stored successfully")
|
logger.debug("Connection timestamp stored successfully")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error connecting to FireflyDB: {e}")
|
logger.error(
|
||||||
|
f"Error connecting to FireflyDB: {e}", exc_info=True
|
||||||
|
)
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
@ -55,6 +65,7 @@ class FireflyDB:
|
|||||||
def close(self):
|
def close(self):
|
||||||
"""Close database connection when done"""
|
"""Close database connection when done"""
|
||||||
if self.db:
|
if self.db:
|
||||||
|
logger.info("Closing database connection")
|
||||||
self.db.close()
|
self.db.close()
|
||||||
self.db = None
|
self.db = None
|
||||||
|
|
||||||
@ -65,17 +76,20 @@ class FireflyDB:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
key = f"audio:{file_path}"
|
key = f"audio:{file_path}"
|
||||||
# Use hash_exists to check if the key exists in the database
|
# Use hash_field_exists to check if the key exists in the database
|
||||||
exists = self.db.hash_ops.hash_exists(key, "title")
|
exists = self.db.hash_ops.hash_field_exists(key, "title")
|
||||||
|
logger.debug(
|
||||||
|
f"Metadata check for {file_path}: {'exists' if exists else 'not found'}"
|
||||||
|
)
|
||||||
return exists
|
return exists
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error checking metadata in database: {e}")
|
logger.error(f"Error checking metadata in database: {e}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def store_metadata(self, metadata):
|
def store_metadata(self, metadata):
|
||||||
"""Store audio file metadata in the database"""
|
"""Store audio file metadata in the database"""
|
||||||
if not self.use_db or not self.db:
|
if not self.use_db or not self.db:
|
||||||
print("Database usage is disabled, not storing metadata")
|
logger.info("Database usage is disabled, not storing metadata")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -84,42 +98,42 @@ class FireflyDB:
|
|||||||
key = f"audio:{file_path}"
|
key = f"audio:{file_path}"
|
||||||
|
|
||||||
# Log the storage attempt
|
# Log the storage attempt
|
||||||
print(f"Storing metadata for {file_path} in FireflyDB")
|
logger.info(f"Storing metadata for {file_path} in FireflyDB")
|
||||||
print(f"Metadata fields: {list(metadata.keys())}")
|
logger.debug(f"Metadata fields: {list(metadata.keys())}")
|
||||||
|
|
||||||
# Store as a hash with all metadata fields
|
# Store as a hash with all metadata fields
|
||||||
success = True
|
success = True
|
||||||
for field, value in metadata.items():
|
for field, value in metadata.items():
|
||||||
if field != "file_path": # Skip using file_path as a field
|
if field != "file_path": # Skip using file_path as a field
|
||||||
print(f" Setting field {field}={value}")
|
logger.debug(f" Setting field {field}={value}")
|
||||||
# Use the direct hash_set method instead of hash_ops
|
# Use the direct hash_set method instead of hash_ops
|
||||||
result = self.db.hash_ops.hash_set(key, field, value)
|
result = self.db.hash_ops.hash_set(key, field, value)
|
||||||
if not result:
|
if not result:
|
||||||
print(
|
logger.warning(
|
||||||
f" Failed to store field {field} for {file_path}"
|
f" Failed to store field {field} for {file_path}"
|
||||||
)
|
)
|
||||||
success = False
|
success = False
|
||||||
else:
|
else:
|
||||||
print(f" Successfully stored field {field}")
|
logger.debug(f" Successfully stored field {field}")
|
||||||
|
|
||||||
# Add to index lists for quick lookup
|
# Add to index lists for quick lookup
|
||||||
if "artist" in metadata and metadata["artist"]:
|
if "artist" in metadata and metadata["artist"]:
|
||||||
artist_key = f"index:artist:{metadata['artist']}"
|
artist_key = f"index:artist:{metadata['artist']}"
|
||||||
print(f" Adding to artist index: {artist_key}")
|
logger.debug(f" Adding to artist index: {artist_key}")
|
||||||
self.db.list_ops.list_right_push(artist_key, file_path)
|
self.db.list_ops.list_right_push(artist_key, file_path)
|
||||||
|
|
||||||
if "album" in metadata and metadata["album"]:
|
if "album" in metadata and metadata["album"]:
|
||||||
album_key = f"index:album:{metadata['album']}"
|
album_key = f"index:album:{metadata['album']}"
|
||||||
print(f" Adding to album index: {album_key}")
|
logger.debug(f" Adding to album index: {album_key}")
|
||||||
self.db.list_ops.list_right_push(album_key, file_path)
|
self.db.list_ops.list_right_push(album_key, file_path)
|
||||||
|
|
||||||
if "genre" in metadata and metadata["genre"]:
|
if "genre" in metadata and metadata["genre"]:
|
||||||
genre_key = f"index:genre:{metadata['genre']}"
|
genre_key = f"index:genre:{metadata['genre']}"
|
||||||
print(f" Adding to genre index: {genre_key}")
|
logger.debug(f" Adding to genre index: {genre_key}")
|
||||||
self.db.list_ops.list_right_push(genre_key, file_path)
|
self.db.list_ops.list_right_push(genre_key, file_path)
|
||||||
|
|
||||||
# Add to a master list of all audio files for easy retrieval
|
# Add to a master list of all audio files for easy retrieval
|
||||||
print(" Adding to master audio files list")
|
logger.debug(" Adding to master audio files list")
|
||||||
self.db.list_ops.list_right_push("all_audio_files", file_path)
|
self.db.list_ops.list_right_push("all_audio_files", file_path)
|
||||||
|
|
||||||
# Store timestamp of when metadata was added
|
# Store timestamp of when metadata was added
|
||||||
@ -128,19 +142,38 @@ class FireflyDB:
|
|||||||
# Verify storage by retrieving one field
|
# Verify storage by retrieving one field
|
||||||
if "title" in metadata:
|
if "title" in metadata:
|
||||||
retrieved_title = self.db.hash_ops.hash_get(key, "title")
|
retrieved_title = self.db.hash_ops.hash_get(key, "title")
|
||||||
print(f" Verification - Retrieved title: {retrieved_title}")
|
logger.debug(
|
||||||
|
f" Verification - Retrieved title: {retrieved_title}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Strip quotes if present in the retrieved value
|
||||||
|
if retrieved_title and isinstance(retrieved_title, str):
|
||||||
|
if retrieved_title.startswith(
|
||||||
|
'"'
|
||||||
|
) and retrieved_title.endswith('"'):
|
||||||
|
retrieved_title = retrieved_title[1:-1]
|
||||||
|
|
||||||
|
# Compare the cleaned value
|
||||||
if retrieved_title != metadata["title"]:
|
if retrieved_title != metadata["title"]:
|
||||||
print(
|
logger.warning(
|
||||||
f" Verification failed: expected '{metadata['title']}', got '{retrieved_title}'"
|
f" Verification failed: expected '{metadata['title']}', got '{retrieved_title}'"
|
||||||
)
|
)
|
||||||
success = False
|
# Don't mark as failure if it's just a quoting issue
|
||||||
|
if retrieved_title.strip('"') == metadata["title"]:
|
||||||
|
logger.info(
|
||||||
|
" Verification passed after stripping quotes"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
success = False
|
||||||
|
|
||||||
print(
|
logger.info(
|
||||||
f"Metadata storage {'successful' if success else 'partially failed'} for {file_path}"
|
f"Metadata storage {'successful' if success else 'partially failed'} for {file_path}"
|
||||||
)
|
)
|
||||||
return success
|
return success
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error storing metadata in database: {e}")
|
logger.error(
|
||||||
|
f"Error storing metadata in database: {e}", exc_info=True
|
||||||
|
)
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
@ -158,10 +191,14 @@ class FireflyDB:
|
|||||||
if metadata:
|
if metadata:
|
||||||
# Add the file path to the metadata
|
# Add the file path to the metadata
|
||||||
metadata["file_path"] = file_path
|
metadata["file_path"] = file_path
|
||||||
|
logger.debug(
|
||||||
|
f"Retrieved metadata for {file_path} from database"
|
||||||
|
)
|
||||||
return metadata
|
return metadata
|
||||||
|
logger.debug(f"No metadata found for {file_path} in database")
|
||||||
return None
|
return None
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error retrieving metadata from database: {e}")
|
logger.error(f"Error retrieving metadata from database: {e}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def search_by_artist(self, artist):
|
def search_by_artist(self, artist):
|
||||||
@ -171,9 +208,10 @@ class FireflyDB:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
key = f"index:artist:{artist}"
|
key = f"index:artist:{artist}"
|
||||||
return self.db.list_range(key, 0, -1)
|
logger.debug(f"Searching for files by artist: {artist}")
|
||||||
|
return self.db.list_ops.list_range(key, 0, -1)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error searching by artist: {e}")
|
logger.error(f"Error searching by artist: {e}")
|
||||||
return []
|
return []
|
||||||
|
|
||||||
# Similar methods for album and genre searches
|
# Similar methods for album and genre searches
|
||||||
@ -191,27 +229,31 @@ class FireflyDB:
|
|||||||
|
|
||||||
# Store in database if enabled
|
# Store in database if enabled
|
||||||
if self.use_db and self.db:
|
if self.use_db and self.db:
|
||||||
|
logger.debug(
|
||||||
|
f"Storing metadata for {metadata.get('file_path', 'unknown file')} in database"
|
||||||
|
)
|
||||||
db_success = self.store_metadata(metadata)
|
db_success = self.store_metadata(metadata)
|
||||||
if db_success:
|
if db_success:
|
||||||
print(
|
logger.info(
|
||||||
f"Successfully stored metadata for {metadata.get('file_path', 'unknown file')} in database"
|
f"Successfully stored metadata for {metadata.get('file_path', 'unknown file')} in database"
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
print(
|
logger.warning(
|
||||||
f"Failed to store metadata for {metadata.get('file_path', 'unknown file')} in database"
|
f"Failed to store metadata for {metadata.get('file_path', 'unknown file')} in database"
|
||||||
)
|
)
|
||||||
|
|
||||||
def verify_database_connection(self):
|
def verify_database_connection(self):
|
||||||
"""Verify that the database connection is working properly"""
|
"""Verify that the database connection is working properly"""
|
||||||
if not self.use_db or not self.db:
|
if not self.use_db or not self.db:
|
||||||
print("Database usage is disabled")
|
logger.info("Database usage is disabled")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Test ping
|
# Test ping
|
||||||
|
logger.debug("Testing database connection with ping")
|
||||||
ping_result = self.db.ping()
|
ping_result = self.db.ping()
|
||||||
if not ping_result:
|
if not ping_result:
|
||||||
print("Database ping failed")
|
logger.warning("Database ping failed")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Test basic operations
|
# Test basic operations
|
||||||
@ -219,14 +261,17 @@ class FireflyDB:
|
|||||||
test_value = "connection_test"
|
test_value = "connection_test"
|
||||||
|
|
||||||
# Test string operations
|
# Test string operations
|
||||||
string_set_result = self.db.string_set(test_key, test_value)
|
logger.debug("Testing string operations")
|
||||||
|
string_set_result = self.db.string_ops.string_set(
|
||||||
|
test_key, test_value
|
||||||
|
)
|
||||||
if not string_set_result:
|
if not string_set_result:
|
||||||
print("Failed to set test string in database")
|
logger.warning("Failed to set test string in database")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
string_get_result = self.db.string_get(test_key)
|
string_get_result = self.db.string_get(test_key)
|
||||||
if string_get_result != test_value:
|
if string_get_result != test_value:
|
||||||
print(
|
logger.warning(
|
||||||
f"String get test failed. Expected '{test_value}', got '{string_get_result}'"
|
f"String get test failed. Expected '{test_value}', got '{string_get_result}'"
|
||||||
)
|
)
|
||||||
return False
|
return False
|
||||||
@ -234,8 +279,10 @@ class FireflyDB:
|
|||||||
# Clean up
|
# Clean up
|
||||||
self.db.delete(test_key)
|
self.db.delete(test_key)
|
||||||
|
|
||||||
print("Database connection verified successfully")
|
logger.info("Database connection verified successfully")
|
||||||
return True
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Database verification failed with error: {e}")
|
logger.error(
|
||||||
|
f"Database verification failed with error: {e}", exc_info=True
|
||||||
|
)
|
||||||
return False
|
return False
|
||||||
|
|||||||
44
logging_setup.py
Normal file
44
logging_setup.py
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import logging
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
|
||||||
|
def setup_logging(args=None):
|
||||||
|
"""
|
||||||
|
Configure logging based on command line arguments
|
||||||
|
|
||||||
|
Args:
|
||||||
|
args: The parsed command line arguments
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A configured logger instance
|
||||||
|
"""
|
||||||
|
# Create logger
|
||||||
|
logger = logging.getLogger("fbroswer")
|
||||||
|
|
||||||
|
# Set level based on debug flag
|
||||||
|
if args and args.debug_on:
|
||||||
|
logger.setLevel(logging.DEBUG)
|
||||||
|
# Create more verbose formatter for debug mode
|
||||||
|
formatter = logging.Formatter(
|
||||||
|
"%(asctime)s - %(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
logger.setLevel(logging.CRITICAL)
|
||||||
|
# Create simpler formatter for normal operation
|
||||||
|
formatter = logging.Formatter(
|
||||||
|
"%(asctime)s - %(levelname)s - %(message)s"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create console handler
|
||||||
|
console_handler = logging.StreamHandler()
|
||||||
|
console_handler.setFormatter(formatter)
|
||||||
|
|
||||||
|
# Add handler to logger
|
||||||
|
logger.addHandler(console_handler)
|
||||||
|
|
||||||
|
# Optionally add file handler for persistent logs
|
||||||
|
file_handler = logging.FileHandler("fbroswer.log")
|
||||||
|
file_handler.setFormatter(formatter)
|
||||||
|
logger.addHandler(file_handler)
|
||||||
|
|
||||||
|
return logger
|
||||||
@ -1,10 +1,15 @@
|
|||||||
import os
|
import os
|
||||||
|
import logging
|
||||||
import concurrent.futures
|
import concurrent.futures
|
||||||
import mutagen
|
import mutagen
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from PyQt6.QtCore import QThread, pyqtSignal
|
from PyQt6.QtCore import QThread, pyqtSignal
|
||||||
|
|
||||||
|
|
||||||
|
# Get the logger
|
||||||
|
logger = logging.getLogger("fbroswer")
|
||||||
|
|
||||||
|
|
||||||
# Metadata Extractor
|
# Metadata Extractor
|
||||||
class MetadataExtractor(QThread):
|
class MetadataExtractor(QThread):
|
||||||
metadata_extracted = pyqtSignal(dict)
|
metadata_extracted = pyqtSignal(dict)
|
||||||
@ -24,16 +29,24 @@ class MetadataExtractor(QThread):
|
|||||||
self.genres = set()
|
self.genres = set()
|
||||||
self.years = set()
|
self.years = set()
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
f"MetadataExtractor initialized with {len(file_list)} files"
|
||||||
|
)
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
total_files = len(self.file_list)
|
total_files = len(self.file_list)
|
||||||
processed_files = 0
|
processed_files = 0
|
||||||
|
|
||||||
|
logger.info(f"Starting metadata extraction for {total_files} files")
|
||||||
|
|
||||||
with concurrent.futures.ThreadPoolExecutor() as executor:
|
with concurrent.futures.ThreadPoolExecutor() as executor:
|
||||||
futures = []
|
futures = []
|
||||||
for file_path in self.file_list:
|
for file_path in self.file_list:
|
||||||
if self.stop_requested:
|
if self.stop_requested:
|
||||||
|
logger.debug("Metadata extraction stopped by user request")
|
||||||
break
|
break
|
||||||
if file_path in self.metadata_cache:
|
if file_path in self.metadata_cache:
|
||||||
|
logger.debug(f"Using cached metadata for {file_path}")
|
||||||
self.metadata_extracted.emit(
|
self.metadata_extracted.emit(
|
||||||
self.metadata_cache[file_path]
|
self.metadata_cache[file_path]
|
||||||
)
|
)
|
||||||
@ -48,36 +61,68 @@ class MetadataExtractor(QThread):
|
|||||||
|
|
||||||
for future in concurrent.futures.as_completed(futures):
|
for future in concurrent.futures.as_completed(futures):
|
||||||
if self.stop_requested:
|
if self.stop_requested:
|
||||||
|
logger.debug(
|
||||||
|
"Metadata extraction stopped during processing"
|
||||||
|
)
|
||||||
break
|
break
|
||||||
try:
|
try:
|
||||||
metadata = future.result()
|
metadata = future.result()
|
||||||
if metadata:
|
if metadata:
|
||||||
self.metadata_extracted.emit(metadata)
|
self.metadata_extracted.emit(metadata)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error extracting metadata: {e}")
|
logger.error(
|
||||||
|
f"Error extracting metadata: {e}", exc_info=True
|
||||||
|
)
|
||||||
|
|
||||||
processed_files += 1
|
processed_files += 1
|
||||||
self.progress_update.emit(
|
progress = int(processed_files / total_files * 100)
|
||||||
int(processed_files / total_files * 100)
|
logger.debug(f"Metadata extraction progress: {progress}%")
|
||||||
)
|
self.progress_update.emit(progress)
|
||||||
|
|
||||||
|
logger.info("Metadata extraction complete")
|
||||||
self.extraction_complete.emit()
|
self.extraction_complete.emit()
|
||||||
|
|
||||||
def extract_metadata(self, file_path):
|
def extract_metadata(self, file_path):
|
||||||
try:
|
try:
|
||||||
if not os.path.isfile(file_path):
|
if not os.path.isfile(file_path):
|
||||||
|
logger.warning(f"File does not exist: {file_path}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Skip non-audio files
|
# Skip non-audio files
|
||||||
if not file_path.lower().endswith(
|
if not file_path.lower().endswith(
|
||||||
(".mp3", ".wav", ".flac", ".m4a", ".wma", ".mid", ".midi")
|
(".mp3", ".wav", ".flac", ".m4a", ".wma", ".mid", ".midi")
|
||||||
):
|
):
|
||||||
|
logger.debug(f"Skipping non-audio file: {file_path}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
print(f"Extracting metadata for {file_path}")
|
logger.info(f"Extracting metadata for {file_path}")
|
||||||
audio = mutagen.File(file_path) # type: ignore
|
|
||||||
|
# Add additional error handling for mutagen
|
||||||
|
try:
|
||||||
|
audio = mutagen.File(file_path) # type: ignore
|
||||||
|
except (OSError, IOError) as e:
|
||||||
|
logger.error(f"Mutagen error reading file {file_path}: {e}")
|
||||||
|
# Return basic metadata without audio tags
|
||||||
|
file_size_mb = os.path.getsize(file_path) / (1024 * 1024)
|
||||||
|
return {
|
||||||
|
"file_path": file_path,
|
||||||
|
"artist": "Unknown Artist",
|
||||||
|
"album": "Unknown Album",
|
||||||
|
"title": os.path.basename(file_path),
|
||||||
|
"genre": "Unknown Genre",
|
||||||
|
"year": "Unknown Year",
|
||||||
|
"size_mb": f"{file_size_mb:.2f}",
|
||||||
|
"filename": os.path.basename(file_path),
|
||||||
|
"extension": os.path.splitext(file_path)[1].lower(),
|
||||||
|
"last_modified": str(
|
||||||
|
datetime.fromtimestamp(os.path.getmtime(file_path))
|
||||||
|
),
|
||||||
|
"extracted_at": str(datetime.now()),
|
||||||
|
"error": str(e),
|
||||||
|
}
|
||||||
|
|
||||||
if not audio:
|
if not audio:
|
||||||
print(f"No metadata found for {file_path}")
|
logger.warning(f"No metadata found for {file_path}")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Get file size in MB
|
# Get file size in MB
|
||||||
@ -101,14 +146,14 @@ class MetadataExtractor(QThread):
|
|||||||
"extracted_at": str(datetime.now()),
|
"extracted_at": str(datetime.now()),
|
||||||
}
|
}
|
||||||
|
|
||||||
print(f"Extracted metadata: {metadata}")
|
logger.debug(f"Extracted metadata: {metadata}")
|
||||||
|
|
||||||
# Cache the result
|
# Cache the result
|
||||||
self.metadata_cache[file_path] = metadata
|
self.metadata_cache[file_path] = metadata
|
||||||
|
|
||||||
return metadata
|
return metadata
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error processing {file_path}: {e}")
|
logger.error(f"Error processing {file_path}: {e}", exc_info=True)
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
@ -122,19 +167,20 @@ class MetadataExtractor(QThread):
|
|||||||
if isinstance(value, list) and len(value) > 0:
|
if isinstance(value, list) and len(value) > 0:
|
||||||
return str(value[0])
|
return str(value[0])
|
||||||
return str(value)
|
return str(value)
|
||||||
except Exception:
|
except Exception as e:
|
||||||
pass
|
logger.debug(f"Error extracting tag {tag_name}: {e}")
|
||||||
return default_value
|
return default_value
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
|
logger.info("Stopping metadata extraction")
|
||||||
self.stop_requested = True
|
self.stop_requested = True
|
||||||
|
|
||||||
def metadata_extraction_complete(self):
|
def metadata_extraction_complete(self):
|
||||||
"""Handle metadata extraction completion"""
|
"""Handle metadata extraction completion"""
|
||||||
print(
|
logger.info(
|
||||||
f"Metadata extraction complete. Artists: {len(self.artists)},\
|
f"Metadata extraction complete. Artists: {len(self.artists)}, "
|
||||||
Albums: {len(self.albums)}, Genres: {len(self.genres)},\
|
f"Albums: {len(self.albums)}, Genres: {len(self.genres)}, "
|
||||||
Years: {len(self.years)}"
|
f"Years: {len(self.years)}"
|
||||||
)
|
)
|
||||||
if self.on_metadata_complete:
|
if self.on_metadata_complete:
|
||||||
self.on_metadata_complete()
|
self.on_metadata_complete()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user