From 7e7754f533c70fc6987b96c56b0e78cbbbbac62f Mon Sep 17 00:00:00 2001 From: nmemmert Date: Mon, 17 Nov 2025 11:53:32 -0500 Subject: [PATCH] Add Playlist Analytics Dashboard with charts for growth, top artists, and update frequency using matplotlib --- journey_fm_app.py | 138 +++++++++++++++++++++++++++++++++++++++++----- requirements.txt | 3 +- 2 files changed, 127 insertions(+), 14 deletions(-) diff --git a/journey_fm_app.py b/journey_fm_app.py index 60c533b..2e6d2cc 100644 --- a/journey_fm_app.py +++ b/journey_fm_app.py @@ -15,19 +15,17 @@ from datetime import datetime from pathlib import Path # GUI imports -from PySide6.QtWidgets import ( - QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, - QLabel, QPushButton, QTextEdit, QLineEdit, QFormLayout, - QDialog, QDialogButtonBox, QSystemTrayIcon, QMenu, - QGroupBox, QCheckBox, QSpinBox, QComboBox, QMessageBox, - QProgressBar, QSplitter, QFrame, QScrollArea, QTextBrowser, QTableWidget, QTableWidgetItem, QHeaderView, QListWidget, QListWidgetItem -) -from PySide6.QtCore import ( - Qt, QTimer, QThread, Signal, QSettings, QSize -) -from PySide6.QtGui import ( - QIcon, QAction, QFont, QPixmap, QPainter, QColor, QDesktopServices -) +from PySide6.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget, QPushButton, QTextEdit, QLineEdit, QLabel, QHBoxLayout, QDialog, QListWidget, QListWidgetItem, QTableWidget, QTableWidgetItem, QHeaderView, QCheckBox, QInputDialog, QMessageBox, QProgressBar, QSystemTrayIcon, QMenu, QAction, QComboBox, QGroupBox, QFormLayout, QSpinBox, QTextBrowser, QTabWidget +from PySide6.QtCore import QTimer, Qt, QThread, Signal, QSettings, QUrl +from PySide6.QtGui import QIcon, QDesktopServices, QFont +import json +import csv +from urllib.parse import quote +import sqlite3 +from datetime import datetime +import matplotlib.pyplot as plt +from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas +from matplotlib.figure import Figure # Import our existing functionality from main import scrape_recently_played, create_playlist_in_plex, PLEX_TOKEN, SERVER_IP, PLAYLIST_NAME @@ -309,6 +307,11 @@ class MainWindow(QMainWindow): self.stats_button.clicked.connect(self.show_statistics) status_layout.addWidget(self.stats_button) + # Analytics button + self.analytics_button = QPushButton("Analytics") + self.analytics_button.clicked.connect(self.show_analytics) + status_layout.addWidget(self.analytics_button) + status_group.setLayout(status_layout) splitter.addWidget(status_group) @@ -628,6 +631,115 @@ class MainWindow(QMainWindow): dialog.setLayout(layout) dialog.exec() + def show_analytics(self): + """Show analytics dashboard with charts""" + import sqlite3 + import json + from datetime import datetime + from collections import Counter + + dialog = QDialog(self) + dialog.setWindowTitle("Playlist Analytics") + dialog.setModal(True) + dialog.resize(1000, 700) + + layout = QVBoxLayout() + + tab_widget = QTabWidget() + + try: + conn = sqlite3.connect('playlist_history.db') + c = conn.cursor() + c.execute('SELECT date, added_songs, missing_songs FROM history ORDER BY date') + rows = c.fetchall() + conn.close() + + # Prepare data + dates = [] + cumulative_added = 0 + cumulative_list = [] + artist_counter = Counter() + update_freq = [] + + for date, added_json, missing_json in rows: + dt = datetime.fromisoformat(date) + dates.append(dt.strftime('%Y-%m-%d')) + + added_songs = json.loads(added_json) if added_json else [] + cumulative_added += len(added_songs) + cumulative_list.append(cumulative_added) + update_freq.append(len(added_songs)) + + for song in added_songs: + # Parse artist from "Title by Artist" + if " by " in song: + artist = song.split(" by ", 1)[1] + artist_counter[artist] += 1 + + # Tab 1: Playlist Growth + growth_tab = QWidget() + growth_layout = QVBoxLayout() + fig1 = Figure(figsize=(8, 6)) + ax1 = fig1.add_subplot(111) + ax1.plot(dates, cumulative_list, marker='o') + ax1.set_title('Playlist Growth Over Time') + ax1.set_xlabel('Date') + ax1.set_ylabel('Total Songs') + ax1.tick_params(axis='x', rotation=45) + canvas1 = FigureCanvas(fig1) + growth_layout.addWidget(canvas1) + growth_tab.setLayout(growth_layout) + tab_widget.addTab(growth_tab, "Growth") + + # Tab 2: Top Artists + artists_tab = QWidget() + artists_layout = QVBoxLayout() + fig2 = Figure(figsize=(8, 6)) + ax2 = fig2.add_subplot(111) + top_artists = artist_counter.most_common(10) + if top_artists: + artists, counts = zip(*top_artists) + ax2.bar(artists, counts) + ax2.set_title('Top 10 Artists') + ax2.set_xlabel('Artist') + ax2.set_ylabel('Songs Added') + ax2.tick_params(axis='x', rotation=45) + canvas2 = FigureCanvas(fig2) + artists_layout.addWidget(canvas2) + artists_tab.setLayout(artists_layout) + tab_widget.addTab(artists_tab, "Top Artists") + + # Tab 3: Update Frequency + freq_tab = QWidget() + freq_layout = QVBoxLayout() + fig3 = Figure(figsize=(8, 6)) + ax3 = fig3.add_subplot(111) + ax3.bar(dates, update_freq) + ax3.set_title('Songs Added per Update') + ax3.set_xlabel('Date') + ax3.set_ylabel('Songs Added') + ax3.tick_params(axis='x', rotation=45) + canvas3 = FigureCanvas(fig3) + freq_layout.addWidget(canvas3) + freq_tab.setLayout(freq_layout) + tab_widget.addTab(freq_tab, "Update Frequency") + + except Exception as e: + error_tab = QWidget() + error_layout = QVBoxLayout() + error_layout.addWidget(QLabel(f"Error loading analytics: {str(e)}")) + error_tab.setLayout(error_layout) + tab_widget.addTab(error_tab, "Error") + + layout.addWidget(tab_widget) + + buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Close) + buttons.rejected.connect(dialog.close) + layout.addWidget(buttons) + + dialog.setLayout(layout) + dialog.exec() + def show_history(self): """Show the playlist history dialog""" import sqlite3 diff --git a/requirements.txt b/requirements.txt index c2349f6..b8a64e6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,4 +4,5 @@ plexapi mutagen selenium webdriver-manager -PySide6 \ No newline at end of file +PySide6 +matplotlib \ No newline at end of file