This commit introduces database integration using FireflyDB for storing and retrieving audio file metadata. - Integrated FireflyDB for persistent storage of metadata. - Added methods to check for existing metadata in the database and retrieve it. - Modified the Organizer class to use FireflyDB for processing metadata. - Added auto-scanning and metadata extraction upon directory opening in Fbrowser. - Created archiver.py and metaextract.py to house the ArchiveExtractor and MetadataExtractor classes respectively. - Added .gitignore entries for Firefly related files. - Added Mock MIT License, Contributor License Agreement, and Pro Edition License Agreement files.
242 lines
8.9 KiB
Python
242 lines
8.9 KiB
Python
from datetime import datetime
|
|
from ifireflylib import IFireflyClient as FireflyDatabase
|
|
|
|
|
|
class FireflyDB:
|
|
def __init__(self):
|
|
# Initialize with default values
|
|
self.use_db = False
|
|
self.db = None
|
|
self.artists = set()
|
|
self.albums = set()
|
|
self.genres = set()
|
|
self.years = set()
|
|
|
|
def connect_to(
|
|
self, use_db=False, db_host="localhost", db_port=6379, db_password=None
|
|
):
|
|
# Set the use_db flag
|
|
self.use_db = use_db
|
|
|
|
# Metadata organization
|
|
self.artists = set()
|
|
self.albums = set()
|
|
self.genres = set()
|
|
self.years = set()
|
|
|
|
if use_db:
|
|
try:
|
|
print(f"Connecting to FireflyDB at {db_host}:{db_port}")
|
|
self.db = FireflyDatabase(
|
|
host=db_host, port=db_port, password=db_password
|
|
)
|
|
# Test connection
|
|
if not self.db.ping():
|
|
print(
|
|
"Warning: Could not connect to FireflyDB. Continuing without database."
|
|
)
|
|
self.use_db = False
|
|
self.db = None
|
|
else:
|
|
print("Successfully connected to FireflyDB")
|
|
# Store connection timestamp
|
|
timestamp = str(datetime.now())
|
|
print(f"Setting last_connection timestamp: {timestamp}")
|
|
self.db.string_ops.string_set("last_connection", timestamp)
|
|
print("Connection timestamp stored successfully")
|
|
except Exception as e:
|
|
print(f"Error connecting to FireflyDB: {e}")
|
|
import traceback
|
|
|
|
traceback.print_exc()
|
|
self.use_db = False
|
|
self.db = None
|
|
|
|
def close(self):
|
|
"""Close database connection when done"""
|
|
if self.db:
|
|
self.db.close()
|
|
self.db = None
|
|
|
|
def has_metadata_in_db(self, file_path):
|
|
"""Check if metadata for a file already exists in the database"""
|
|
if not self.use_db or not self.db:
|
|
return False
|
|
|
|
try:
|
|
key = f"audio:{file_path}"
|
|
# Use hash_exists to check if the key exists in the database
|
|
exists = self.db.hash_ops.hash_exists(key, "title")
|
|
return exists
|
|
except Exception as e:
|
|
print(f"Error checking metadata in database: {e}")
|
|
return False
|
|
|
|
def store_metadata(self, metadata):
|
|
"""Store audio file metadata in the database"""
|
|
if not self.use_db or not self.db:
|
|
print("Database usage is disabled, not storing metadata")
|
|
return False
|
|
|
|
try:
|
|
# Use the file path as a unique key
|
|
file_path = metadata["file_path"]
|
|
key = f"audio:{file_path}"
|
|
|
|
# Log the storage attempt
|
|
print(f"Storing metadata for {file_path} in FireflyDB")
|
|
print(f"Metadata fields: {list(metadata.keys())}")
|
|
|
|
# Store as a hash with all metadata fields
|
|
success = True
|
|
for field, value in metadata.items():
|
|
if field != "file_path": # Skip using file_path as a field
|
|
print(f" Setting field {field}={value}")
|
|
# Use the direct hash_set method instead of hash_ops
|
|
result = self.db.hash_ops.hash_set(key, field, value)
|
|
if not result:
|
|
print(
|
|
f" Failed to store field {field} for {file_path}"
|
|
)
|
|
success = False
|
|
else:
|
|
print(f" Successfully stored field {field}")
|
|
|
|
# Add to index lists for quick lookup
|
|
if "artist" in metadata and metadata["artist"]:
|
|
artist_key = f"index:artist:{metadata['artist']}"
|
|
print(f" Adding to artist index: {artist_key}")
|
|
self.db.list_ops.list_right_push(artist_key, file_path)
|
|
|
|
if "album" in metadata and metadata["album"]:
|
|
album_key = f"index:album:{metadata['album']}"
|
|
print(f" Adding to album index: {album_key}")
|
|
self.db.list_ops.list_right_push(album_key, file_path)
|
|
|
|
if "genre" in metadata and metadata["genre"]:
|
|
genre_key = f"index:genre:{metadata['genre']}"
|
|
print(f" Adding to genre index: {genre_key}")
|
|
self.db.list_ops.list_right_push(genre_key, file_path)
|
|
|
|
# Add to a master list of all audio files for easy retrieval
|
|
print(" Adding to master audio files list")
|
|
self.db.list_ops.list_right_push("all_audio_files", file_path)
|
|
|
|
# Store timestamp of when metadata was added
|
|
self.db.hash_ops.hash_set(key, "timestamp", str(datetime.now()))
|
|
|
|
# Verify storage by retrieving one field
|
|
if "title" in metadata:
|
|
retrieved_title = self.db.hash_ops.hash_get(key, "title")
|
|
print(f" Verification - Retrieved title: {retrieved_title}")
|
|
if retrieved_title != metadata["title"]:
|
|
print(
|
|
f" Verification failed: expected '{metadata['title']}', got '{retrieved_title}'"
|
|
)
|
|
success = False
|
|
|
|
print(
|
|
f"Metadata storage {'successful' if success else 'partially failed'} for {file_path}"
|
|
)
|
|
return success
|
|
except Exception as e:
|
|
print(f"Error storing metadata in database: {e}")
|
|
import traceback
|
|
|
|
traceback.print_exc()
|
|
return False
|
|
|
|
def get_metadata_from_db(self, file_path):
|
|
"""Retrieve metadata for a file from the database"""
|
|
if not self.use_db or not self.db:
|
|
return None
|
|
|
|
try:
|
|
key = f"audio:{file_path}"
|
|
metadata = self.db.hash_ops.hash_get_all(key)
|
|
|
|
if metadata:
|
|
# Add the file path to the metadata
|
|
metadata["file_path"] = file_path
|
|
return metadata
|
|
return None
|
|
except Exception as e:
|
|
print(f"Error retrieving metadata from database: {e}")
|
|
return None
|
|
|
|
def search_by_artist(self, artist):
|
|
"""Search for files by artist"""
|
|
if not self.use_db or not self.db:
|
|
return []
|
|
|
|
try:
|
|
key = f"index:artist:{artist}"
|
|
return self.db.list_range(key, 0, -1)
|
|
except Exception as e:
|
|
print(f"Error searching by artist: {e}")
|
|
return []
|
|
|
|
# Similar methods for album and genre searches
|
|
|
|
def process_metadata(self, metadata):
|
|
"""Process extracted metadata"""
|
|
if "artist" in metadata and metadata["artist"]:
|
|
self.artists.add(metadata["artist"])
|
|
if "album" in metadata and metadata["album"]:
|
|
self.albums.add(metadata["album"])
|
|
if "genre" in metadata and metadata["genre"]:
|
|
self.genres.add(metadata["genre"])
|
|
if "year" in metadata and metadata["year"]:
|
|
self.years.add(metadata["year"])
|
|
|
|
# Store in database if enabled
|
|
if self.use_db and self.db:
|
|
db_success = self.store_metadata(metadata)
|
|
if db_success:
|
|
print(
|
|
f"Successfully stored metadata for {metadata.get('file_path', 'unknown file')} in database"
|
|
)
|
|
else:
|
|
print(
|
|
f"Failed to store metadata for {metadata.get('file_path', 'unknown file')} in database"
|
|
)
|
|
|
|
def verify_database_connection(self):
|
|
"""Verify that the database connection is working properly"""
|
|
if not self.use_db or not self.db:
|
|
print("Database usage is disabled")
|
|
return False
|
|
|
|
try:
|
|
# Test ping
|
|
ping_result = self.db.ping()
|
|
if not ping_result:
|
|
print("Database ping failed")
|
|
return False
|
|
|
|
# Test basic operations
|
|
test_key = "test:connection"
|
|
test_value = "connection_test"
|
|
|
|
# Test string operations
|
|
string_set_result = self.db.string_set(test_key, test_value)
|
|
if not string_set_result:
|
|
print("Failed to set test string in database")
|
|
return False
|
|
|
|
string_get_result = self.db.string_get(test_key)
|
|
if string_get_result != test_value:
|
|
print(
|
|
f"String get test failed. Expected '{test_value}', got '{string_get_result}'"
|
|
)
|
|
return False
|
|
|
|
# Clean up
|
|
self.db.delete(test_key)
|
|
|
|
print("Database connection verified successfully")
|
|
return True
|
|
except Exception as e:
|
|
print(f"Database verification failed with error: {e}")
|
|
return False
|