diff --git a/app/models.py b/app/models.py index 8f2a287..1ba9695 100644 --- a/app/models.py +++ b/app/models.py @@ -186,7 +186,7 @@ class Playdates(Base): id: int = Column(Integer, primary_key=True, autoincrement=True) lastplayed: datetime = Column(DateTime, index=True, default=None) track_id: int = Column(Integer, ForeignKey('tracks.id')) - tracks: RelationshipProperty = relationship( + track: RelationshipProperty = relationship( "Tracks", back_populates="playdates", lazy="joined") def __init__(self, session: Session, track_id: int) -> None: @@ -212,6 +212,13 @@ class Playdates(Base): else: return None + @staticmethod + def played_after(session: Session, since: datetime) -> List["Playdates"]: + """Return a list of Playdates objects since passed time""" + + return session.query(Playdates).filter( + Playdates.lastplayed >= since).all() + @staticmethod def remove_track(session: Session, track_id: int) -> None: """ @@ -478,7 +485,8 @@ class Tracks(Base): back_populates="tracks", lazy="joined") playdates: RelationshipProperty = relationship("Playdates", - back_populates="tracks", + back_populates="track" + "", lazy="joined") def __init__( diff --git a/app/musicmuster.py b/app/musicmuster.py index d51eb4b..e9f50e1 100755 --- a/app/musicmuster.py +++ b/app/musicmuster.py @@ -11,7 +11,7 @@ from datetime import datetime from log import DEBUG, EXCEPTION from typing import Callable, Dict, List, Optional, Tuple -from PyQt5.QtCore import QEvent, QProcess, Qt, QTimer, QUrl +from PyQt5.QtCore import QDate, QEvent, QProcess, Qt, QTime, QTimer, QUrl from PyQt5.QtGui import QColor from PyQt5.QtWebEngineWidgets import QWebEngineView as QWebView from PyQt5.QtWidgets import ( @@ -36,6 +36,7 @@ from playlists import PlaylistTab from sqlalchemy.orm.exc import DetachedInstanceError from ui.dlg_search_database_ui import Ui_Dialog from ui.dlg_SelectPlaylist_ui import Ui_dlgSelectPlaylist +from ui.downloadcsv_ui import Ui_DateSelect from ui.main_window_ui import Ui_MainWindow from utilities import create_track_from_file @@ -170,6 +171,8 @@ class Window(QMainWindow, Ui_MainWindow): self.actionAdd_note.triggered.connect(self.create_note) self.action_Clear_selection.triggered.connect(self.clear_selection) self.actionClosePlaylist.triggered.connect(self.close_playlist_tab) + self.actionDownload_CSV_of_played_tracks.triggered.connect( + self.download_played_tracks) self.actionEnable_controls.triggered.connect( self.enable_play_next_controls) self.actionExport_playlist.triggered.connect(self.export_playlist_tab) @@ -279,6 +282,32 @@ class Window(QMainWindow, Ui_MainWindow): self.actionPlay_next.setEnabled(False) self.statusbar.showMessage("Play controls: Disabled", 0) + def download_played_tracks(self) -> None: + """Download a CSV of played tracks""" + + dlg = DownloadCSV(self) + if dlg.exec(): + start_dt = dlg.ui.dateTimeEdit.dateTime().toPyDateTime() + # Get output filename + pathspec: Tuple[str, str] = QFileDialog.getSaveFileName( + self, 'Save CSV of tracks played', + directory="/tmp/playlist.csv", + filter="CSV files (*.csv)" + ) + if not pathspec: + return + + path: str = pathspec[0] + if not path.endswith(".csv"): + path += ".csv" + + with open(path, "w") as f: + with Session() as session: + for playdate in Playdates.played_after(session, start_dt): + f.write( + f"{playdate.track.artist},{playdate.track.title}\n" + ) + def enable_play_next_controls(self) -> None: """ Enable "play next" keyboard controls @@ -957,6 +986,18 @@ class DbDialog(QDialog): self.ui.dbPath.setText(track.path) +class DownloadCSV(QDialog): + def __init__(self, parent=None): + super().__init__(parent) + + self.ui = Ui_DateSelect() + self.ui.setupUi(self) + self.ui.dateTimeEdit.setDate(QDate.currentDate()) + self.ui.dateTimeEdit.setTime(QTime(19, 59, 0)) + self.ui.buttonBox.accepted.connect(self.accept) + self.ui.buttonBox.rejected.connect(self.reject) + + class SelectPlaylistDialog(QDialog): def __init__(self, parent=None, playlists=None, session=None): super().__init__(parent) diff --git a/app/ui/downloadcsv.ui b/app/ui/downloadcsv.ui new file mode 100644 index 0000000..0c4141f --- /dev/null +++ b/app/ui/downloadcsv.ui @@ -0,0 +1,107 @@ + + + DateSelect + + + + 0 + 0 + 280 + 166 + + + + Dialog + + + + + 70 + 110 + 191 + 32 + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + 70 + 60 + 194 + 28 + + + + true + + + + + + 10 + 20 + 261 + 19 + + + + Download CSV of tracks played + + + + + + 15 + 66 + 51 + 19 + + + + Since: + + + + + + + buttonBox + accepted() + DateSelect + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + DateSelect + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/app/ui/downloadcsv_ui.py b/app/ui/downloadcsv_ui.py new file mode 100644 index 0000000..dc231a9 --- /dev/null +++ b/app/ui/downloadcsv_ui.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'ui/downloadcsv.ui' +# +# Created by: PyQt5 UI code generator 5.15.6 +# +# WARNING: Any manual changes made to this file will be lost when pyuic5 is +# run again. Do not edit this file unless you know what you are doing. + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_DateSelect(object): + def setupUi(self, DateSelect): + DateSelect.setObjectName("DateSelect") + DateSelect.resize(280, 166) + self.buttonBox = QtWidgets.QDialogButtonBox(DateSelect) + self.buttonBox.setGeometry(QtCore.QRect(70, 110, 191, 32)) + self.buttonBox.setOrientation(QtCore.Qt.Horizontal) + self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Cancel|QtWidgets.QDialogButtonBox.Ok) + self.buttonBox.setObjectName("buttonBox") + self.dateTimeEdit = QtWidgets.QDateTimeEdit(DateSelect) + self.dateTimeEdit.setGeometry(QtCore.QRect(70, 60, 194, 28)) + self.dateTimeEdit.setCalendarPopup(True) + self.dateTimeEdit.setObjectName("dateTimeEdit") + self.label = QtWidgets.QLabel(DateSelect) + self.label.setGeometry(QtCore.QRect(10, 20, 261, 19)) + self.label.setObjectName("label") + self.label_2 = QtWidgets.QLabel(DateSelect) + self.label_2.setGeometry(QtCore.QRect(15, 66, 51, 19)) + self.label_2.setObjectName("label_2") + + self.retranslateUi(DateSelect) + self.buttonBox.accepted.connect(DateSelect.accept) # type: ignore + self.buttonBox.rejected.connect(DateSelect.reject) # type: ignore + QtCore.QMetaObject.connectSlotsByName(DateSelect) + + def retranslateUi(self, DateSelect): + _translate = QtCore.QCoreApplication.translate + DateSelect.setWindowTitle(_translate("DateSelect", "Dialog")) + self.label.setText(_translate("DateSelect", "Download CSV of tracks played")) + self.label_2.setText(_translate("DateSelect", "Since:")) diff --git a/app/ui/main_window.ui b/app/ui/main_window.ui index 1553706..9572a33 100644 --- a/app/ui/main_window.ui +++ b/app/ui/main_window.ui @@ -786,6 +786,7 @@ border: 1px solid rgb(85, 87, 83); + @@ -1022,6 +1023,11 @@ border: 1px solid rgb(85, 87, 83); Ctrl+Shift+I + + + Download CSV of played tracks... + + diff --git a/app/ui/main_window_ui.py b/app/ui/main_window_ui.py index 4ae1087..b05b061 100644 --- a/app/ui/main_window_ui.py +++ b/app/ui/main_window_ui.py @@ -443,6 +443,8 @@ class Ui_MainWindow(object): self.actionEnable_controls.setObjectName("actionEnable_controls") self.actionImport = QtWidgets.QAction(MainWindow) self.actionImport.setObjectName("actionImport") + self.actionDownload_CSV_of_played_tracks = QtWidgets.QAction(MainWindow) + self.actionDownload_CSV_of_played_tracks.setObjectName("actionDownload_CSV_of_played_tracks") self.menuFile.addAction(self.actionImport) self.menuFile.addSeparator() self.menuFile.addAction(self.actionE_xit) @@ -465,6 +467,7 @@ class Ui_MainWindow(object): self.menuPlaylist.addAction(self.actionSelect_played_tracks) self.menuPlaylist.addAction(self.actionMoveSelected) self.menuPlaylist.addSeparator() + self.menuPlaylist.addAction(self.actionDownload_CSV_of_played_tracks) self.menuPlaylist.addAction(self.actionExport_playlist) self.menu_Music.addAction(self.actionPlay_next) self.menu_Music.addAction(self.actionSkip_next) @@ -553,4 +556,5 @@ class Ui_MainWindow(object): self.actionEnable_controls.setText(_translate("MainWindow", "Enable controls")) self.actionImport.setText(_translate("MainWindow", "Import...")) self.actionImport.setShortcut(_translate("MainWindow", "Ctrl+Shift+I")) + self.actionDownload_CSV_of_played_tracks.setText(_translate("MainWindow", "Download CSV of played tracks...")) import icons_rc