Add playlist history tracking with SQLite database and GUI dialog
This commit is contained in:
1 parent
9a90811dea
commit
e643bb3c98
2 files changed
+117
-26
No files matched your search
+77
-1
@@ -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():
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
Reference in new issue
Block a user