Add playlist history tracking with SQLite database and GUI dialog

This commit is contained in:
nmemmert committed 2025-11-17 11:30:20 -05:00
1 parent 9a90811dea
commit e643bb3c98
2 files changed
+117 -26

No files matched your search

+77 -1
View File
@@ -20,7 +20,7 @@ from PySide6.QtWidgets import (
QLabel, QPushButton, QTextEdit, QLineEdit, QFormLayout, QLabel, QPushButton, QTextEdit, QLineEdit, QFormLayout,
QDialog, QDialogButtonBox, QSystemTrayIcon, QMenu, QDialog, QDialogButtonBox, QSystemTrayIcon, QMenu,
QGroupBox, QCheckBox, QSpinBox, QComboBox, QMessageBox, QGroupBox, QCheckBox, QSpinBox, QComboBox, QMessageBox,
QProgressBar, QSplitter, QFrame, QScrollArea, QTextBrowser QProgressBar, QSplitter, QFrame, QScrollArea, QTextBrowser, QTableWidget, QTableWidgetItem, QHeaderView
) )
from PySide6.QtCore import ( from PySide6.QtCore import (
Qt, QTimer, QThread, Signal, QSettings, QSize Qt, QTimer, QThread, Signal, QSettings, QSize
@@ -294,6 +294,11 @@ class MainWindow(QMainWindow):
self.buy_list_button.clicked.connect(self.show_buy_list) self.buy_list_button.clicked.connect(self.show_buy_list)
status_layout.addWidget(self.buy_list_button) status_layout.addWidget(self.buy_list_button)
# History button
self.history_button = QPushButton("View History")
self.history_button.clicked.connect(self.show_history)
status_layout.addWidget(self.history_button)
status_group.setLayout(status_layout) status_group.setLayout(status_layout)
splitter.addWidget(status_group) splitter.addWidget(status_group)
@@ -472,6 +477,77 @@ class MainWindow(QMainWindow):
dialog.setLayout(layout) dialog.setLayout(layout)
dialog.exec() # Use exec() for modal dialog dialog.exec() # Use exec() for modal dialog
def show_history(self):
"""Show the playlist history dialog"""
import sqlite3
import json
from datetime import datetime
dialog = QDialog(self)
dialog.setWindowTitle("Playlist History")
dialog.setModal(True)
dialog.resize(800, 600)
layout = QVBoxLayout()
table = QTableWidget()
table.setColumnCount(4)
table.setHorizontalHeaderLabels(["Date", "Added Songs", "Missing Songs", "Details"])
table.horizontalHeader().setStretchLastSection(True)
try:
conn = sqlite3.connect('playlist_history.db')
c = conn.cursor()
c.execute('SELECT date, added_count, added_songs, missing_count, missing_songs FROM history ORDER BY date DESC')
rows = c.fetchall()
conn.close()
table.setRowCount(len(rows))
for row_idx, (date, added_count, added_songs_json, missing_count, missing_songs_json) in enumerate(rows):
# Parse date
try:
dt = datetime.fromisoformat(date)
date_str = dt.strftime('%Y-%m-%d %H:%M')
except:
date_str = date
# Added songs
try:
added_songs = json.loads(added_songs_json)
added_text = f"{added_count} songs\n" + "\n".join(added_songs[:3]) # Show first 3
if len(added_songs) > 3:
added_text += f"\n... and {len(added_songs)-3} more"
except:
added_text = f"{added_count} songs"
# Missing songs
try:
missing_songs = json.loads(missing_songs_json)
missing_text = f"{missing_count} songs\n" + "\n".join([f"{s['artist']} - {s['title']}" for s in missing_songs[:3]])
if len(missing_songs) > 3:
missing_text += f"\n... and {len(missing_songs)-3} more"
except:
missing_text = f"{missing_count} songs"
table.setItem(row_idx, 0, QTableWidgetItem(date_str))
table.setItem(row_idx, 1, QTableWidgetItem(added_text))
table.setItem(row_idx, 2, QTableWidgetItem(missing_text))
table.setItem(row_idx, 3, QTableWidgetItem(f"Added: {added_count}, Missing: {missing_count}"))
except Exception as e:
table.setRowCount(1)
table.setItem(0, 0, QTableWidgetItem("Error loading history"))
table.setItem(0, 1, QTableWidgetItem(str(e)))
layout.addWidget(table)
buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Close)
buttons.rejected.connect(dialog.close)
layout.addWidget(buttons)
dialog.setLayout(layout)
dialog.exec()
def closeEvent(self, event): def closeEvent(self, event):
"""Handle window close - minimize to tray instead of closing""" """Handle window close - minimize to tray instead of closing"""
if self.tray_icon and self.tray_icon.isVisible(): if self.tray_icon and self.tray_icon.isVisible():
+40 -25
View File
@@ -22,6 +22,7 @@ from datetime import datetime
import json import json
import subprocess import subprocess
import urllib.parse import urllib.parse
import sqlite3
import urllib.parse import urllib.parse
# Configuration (defaults, will be overridden by config.json) # Configuration (defaults, will be overridden by config.json)
@@ -122,12 +123,13 @@ def find_song_in_library(artist, title, library_path):
return None return None
def create_playlist_in_plex(plex, songs, playlist_name): def create_playlist_in_plex(plex, songs, playlist_name):
"""Create playlist in Plex and return added count and missing songs""" """Create playlist in Plex and return added count, added songs, and missing songs"""
# Assuming Plex has a music library named 'Music' # Assuming Plex has a music library named 'Music'
music_library = plex.library.section('Music') music_library = plex.library.section('Music')
tracks = [] tracks = []
seen_keys = set() # Track which ratingKeys we've already added to prevent duplicates within this run seen_keys = set() # Track which ratingKeys we've already added to prevent duplicates within this run
missing = [] missing = []
added_songs = []
for song in songs: for song in songs:
# Clean title and artist by removing text in parentheses and normalizing spaces # Clean title and artist by removing text in parentheses and normalizing spaces
@@ -198,6 +200,7 @@ def create_playlist_in_plex(plex, songs, playlist_name):
for track in new_tracks: for track in new_tracks:
print(f" + '{track.title}' by {track.artist().title}") print(f" + '{track.title}' by {track.artist().title}")
added = len(new_tracks) added = len(new_tracks)
added_songs.extend([f"{track.title} by {track.artist().title}" for track in new_tracks])
else: else:
print(f"No new songs to add - all {len(tracks)} matching songs already in playlist.") print(f"No new songs to add - all {len(tracks)} matching songs already in playlist.")
added = 0 added = 0
@@ -208,14 +211,17 @@ def create_playlist_in_plex(plex, songs, playlist_name):
for track in tracks: for track in tracks:
print(f" + '{track.title}' by {track.artist().title}") print(f" + '{track.title}' by {track.artist().title}")
added = len(tracks) added = len(tracks)
added_songs.extend([f"{track.title} by {track.artist().title}" for track in tracks])
except Exception as e: except Exception as e:
print(f"Error with playlist: {e}") print(f"Error with playlist: {e}")
added = 0 added = 0
added_songs = []
else: else:
print("No matching tracks found in Plex.") print("No matching tracks found in Plex.")
added = 0 added = 0
added_songs = []
return added, missing return added, added_songs, missing
def prompt_for_config(): def prompt_for_config():
"""Prompt user for configuration variables""" """Prompt user for configuration variables"""
@@ -232,27 +238,30 @@ def prompt_for_config():
json.dump(config, f) json.dump(config, f)
return config return config
def prompt_for_scheduler(): def init_history_db():
"""Prompt user for scheduler setup""" """Initialize the history database"""
setup = input("Do you want to set up a scheduled task? (y/n): ").strip().lower() conn = sqlite3.connect('playlist_history.db')
if setup != 'y': c = conn.cursor()
return None c.execute('''CREATE TABLE IF NOT EXISTS history (
print("Choose schedule type:") id INTEGER PRIMARY KEY,
print("1. Interval (e.g., every 15 minutes)") date TEXT,
print("2. At startup") added_count INTEGER,
print("3. Both") added_songs TEXT,
choice = input("Enter choice (1/2/3): ").strip() missing_count INTEGER,
if choice == '1': missing_songs TEXT
interval = int(input("Enter interval number: ").strip()) )''')
unit = input("Enter unit (Minutes/Hours/Days): ").strip() conn.commit()
return {'Type': 'Interval', 'Interval': interval, 'Unit': unit} conn.close()
elif choice == '2':
return {'Type': 'Startup'} def save_history_entry(added_count, added_songs, missing_count, missing_songs):
elif choice == '3': """Save a history entry to the database"""
return {'Type': 'Both'} conn = sqlite3.connect('playlist_history.db')
else: c = conn.cursor()
print("Invalid choice.") date = datetime.now().isoformat()
return None c.execute('INSERT INTO history (date, added_count, added_songs, missing_count, missing_songs) VALUES (?, ?, ?, ?, ?)',
(date, added_count, json.dumps(added_songs), missing_count, json.dumps(missing_songs)))
conn.commit()
conn.close()
def setup_scheduler(params): def setup_scheduler(params):
"""Run the setup_scheduler.ps1 with parameters""" """Run the setup_scheduler.ps1 with parameters"""
@@ -284,6 +293,9 @@ def main():
print(f"Journey FM Playlist Update - {start_time.strftime('%Y-%m-%d %I:%M:%S %p')}") print(f"Journey FM Playlist Update - {start_time.strftime('%Y-%m-%d %I:%M:%S %p')}")
print(f"{'='*60}\n") print(f"{'='*60}\n")
# Initialize history database
init_history_db()
# Scrape songs # Scrape songs
songs = scrape_recently_played() songs = scrape_recently_played()
print(f"Found {len(songs)} recently played songs.") print(f"Found {len(songs)} recently played songs.")
@@ -324,10 +336,13 @@ def main():
# Create playlist # Create playlist
if songs: if songs:
added, missing = create_playlist_in_plex(plex, songs, PLAYLIST_NAME) added, added_songs, missing = create_playlist_in_plex(plex, songs, PLAYLIST_NAME)
else: else:
print("No songs found.") print("No songs found.")
added, missing = 0, [] added, added_songs, missing = 0, [], []
# Save to history
save_history_entry(added, added_songs, len(missing), missing)
# Create Amazon buy list for missing songs # Create Amazon buy list for missing songs
if missing: if missing: