WIP: clocks working

This commit is contained in:
Keith Edmunds 2022-08-12 21:25:59 +01:00
parent afc27c988d
commit 7d71e8ce64
10 changed files with 789 additions and 797 deletions

View File

@ -42,6 +42,7 @@ class Config(object):
FADE_TIME = 3000 FADE_TIME = 3000
INFO_TAB_TITLE_LENGTH = 15 INFO_TAB_TITLE_LENGTH = 15
INFO_TAB_URL = "https://www.wikipedia.org/w/index.php?search=%s" INFO_TAB_URL = "https://www.wikipedia.org/w/index.php?search=%s"
LAST_PLAYED_TODAY_STRING = "Today"
LOG_LEVEL_STDERR = logging.DEBUG LOG_LEVEL_STDERR = logging.DEBUG
LOG_LEVEL_SYSLOG = logging.DEBUG LOG_LEVEL_SYSLOG = logging.DEBUG
LOG_NAME = "musicmuster" LOG_NAME = "musicmuster"

View File

@ -45,8 +45,9 @@ def Session() -> Generator[scoped_session, None, None]:
function = frame.function function = frame.function
lineno = frame.lineno lineno = frame.lineno
Session = scoped_session(sessionmaker(bind=engine, future=True)) Session = scoped_session(sessionmaker(bind=engine, future=True))
log.debug(f"Session acquired, {file=}, {function=}, {lineno=}, {Session=}") # log.debug(f"Session acquired, {file=}, {function=},
# function{lineno=}, {Session=}")
yield Session yield Session
log.debug(" Session released") # log.debug(" Session released")
Session.commit() Session.commit()
Session.close() Session.close()

View File

@ -4,7 +4,7 @@ import psutil
from config import Config from config import Config
from datetime import datetime from datetime import datetime
from pydub import AudioSegment from pydub import AudioSegment
# from PyQt5.QtWidgets import QMessageBox from PyQt5.QtWidgets import QMessageBox
# from tinytag import TinyTag # from tinytag import TinyTag
from typing import Optional from typing import Optional
# from typing import Dict, Optional, Union # from typing import Dict, Optional, Union
@ -68,7 +68,7 @@ def get_audio_segment(path: str) -> Optional[AudioSegment]:
if path.endswith('.mp3'): if path.endswith('.mp3'):
return AudioSegment.from_mp3(path) return AudioSegment.from_mp3(path)
elif path.endswith('.flac'): elif path.endswith('.flac'):
return AudioSegment.from_file(path, "flac") return AudioSegment.from_file(path, "flac") # type: ignore
except AttributeError: except AttributeError:
return None return None
@ -232,12 +232,12 @@ def open_in_audacity(path: str) -> bool:
do_command(f'Import2: Filename="{path}"') do_command(f'Import2: Filename="{path}"')
return True return True
#
#
# def show_warning(title: str, msg: str) -> None: def show_warning(title: str, msg: str) -> None:
# """Display a warning to user""" """Display a warning to user"""
#
# QMessageBox.warning(None, title, msg, buttons=QMessageBox.Cancel) QMessageBox.warning(None, title, msg, buttons=QMessageBox.Cancel)
def trailing_silence( def trailing_silence(

View File

@ -225,16 +225,14 @@ class Playdates(Base):
f"<Playdates(id={self.id}, track_id={self.track_id} " f"<Playdates(id={self.id}, track_id={self.track_id} "
f"lastplayed={self.lastplayed}>" f"lastplayed={self.lastplayed}>"
) )
#
# def __init__(self, session: Session, track_id: int) -> None: def __init__(self, session: Session, track_id: int) -> None:
# """Record that track was played""" """Record that track was played"""
#
# log.debug(f"add_playdate({track_id=})") self.lastplayed = datetime.now()
# self.track_id = track_id
# self.lastplayed = datetime.now() session.add(self)
# self.track_id = track_id session.commit()
# session.add(self)
# session.flush()
@staticmethod @staticmethod
def last_played(session: Session, track_id: int) -> Optional[datetime]: def last_played(session: Session, track_id: int) -> Optional[datetime]:
@ -430,6 +428,7 @@ class PlaylistRows(Base):
playlist = relationship(Playlists, back_populates="rows") playlist = relationship(Playlists, back_populates="rows")
track_id = Column(Integer, ForeignKey('tracks.id'), nullable=True) track_id = Column(Integer, ForeignKey('tracks.id'), nullable=True)
track = relationship("Tracks", back_populates="playlistrows") track = relationship("Tracks", back_populates="playlistrows")
played = Column(Boolean, nullable=False, index=False, default=False)
def __repr__(self) -> str: def __repr__(self) -> str:
return ( return (
@ -502,12 +501,49 @@ class PlaylistRows(Base):
).scalars().all() ).scalars().all()
for i, plr in enumerate(plrs): for i, plr in enumerate(plrs):
print(f"{i=}, {plr.row_number=}")
plr.row_number = i plr.row_number = i
# Ensure new row numbers are available to the caller # Ensure new row numbers are available to the caller
session.commit() session.commit()
@staticmethod
def get_played_rows(session: Session,
playlist_id: int) -> List[int]:
"""
For passed playlist, return a list of row numbers that
have been played.
"""
plrs = session.execute(
select(PlaylistRows.row_number)
.where(
PlaylistRows.playlist_id == playlist_id,
PlaylistRows.played.is_(True)
)
.order_by(PlaylistRows.row_number)
).scalars().all()
return plrs
@staticmethod
def get_rows_with_tracks(session: Session,
playlist_id: int) -> List[int]:
"""
For passed playlist, return a list of all row numbers that
contain tracks
"""
plrs = session.execute(
select(PlaylistRows.row_number)
.where(
PlaylistRows.playlist_id == playlist_id,
PlaylistRows.track_id.is_not(None)
)
.order_by(PlaylistRows.row_number)
).scalars().all()
return plrs
@staticmethod @staticmethod
def move_to_playlist(session: Session, def move_to_playlist(session: Session,
playlistrow_ids: List[int], playlistrow_ids: List[int],

View File

@ -1,151 +1,126 @@
# import os # import os
# import threading import threading
# import vlc import vlc
# #
# from config import Config from config import Config
# from datetime import datetime from datetime import datetime
# from time import sleep from helpers import file_is_readable
# from typing import Optional
# from log import log.debug, log.error from time import sleep
#
# lock = threading.Lock() from log import log
#
# lock = threading.Lock()
# class Music:
# """
# Manage the playing of music tracks class Music:
# """ """
# Manage the playing of music tracks
# def __init__(self): """
# self.current_track_start_time = None
# self.fading = 0 def __init__(self) -> None:
# self.VLC = vlc.Instance() # self.current_track_start_time = None
# self.player = None # self.fading = 0
# self.track_path = None self.VLC = vlc.Instance()
# self.max_volume = Config.VOLUME_VLC_DEFAULT self.player = None
# # self.track_path = None
# def fade(self): self.max_volume = Config.VOLUME_VLC_DEFAULT
# """
# Fade the currently playing track. def fade(self) -> None:
# """
# The actual management of fading runs in its own thread so as not Fade the currently playing track.
# to hold up the UI during the fade.
# """ The actual management of fading runs in its own thread so as not
# to hold up the UI during the fade.
# log.debug("music.fade()", True) """
#
# if not self.player: if not self.player:
# return return
#
# if not self.player.get_position() > 0 and self.player.is_playing(): if not self.player.get_position() > 0 and self.player.is_playing():
# return return
#
# self.fading += 1 thread = threading.Thread(target=self._fade)
# thread.start()
# thread = threading.Thread(target=self._fade)
# thread.start() def _fade(self) -> None:
# """
# def _fade(self): Implementation of fading the current track in a separate thread.
# """ """
# Implementation of fading the current track in a separate thread.
# """ # Take a copy of current player to allow another track to be
# # started without interfering here
# # Take a copy of current player to allow another track to be with lock:
# # started without interfering here p = self.player
# self.player = None
# log.debug(f"music._fade(), {self.player=}", True)
# # Sanity check
# with lock: if not p:
# p = self.player return
# self.player = None
# fade_time = Config.FADE_TIME / 1000
# log.debug("music._fade() post-lock", True) steps = Config.FADE_STEPS
# sleep_time = fade_time / steps
# fade_time = Config.FADE_TIME / 1000
# steps = Config.FADE_STEPS # We reduce volume by one mesure first, then by two measures,
# sleep_time = fade_time / steps # then three, and so on.
# # The sum of the arithmetic sequence 1, 2, 3, ..n is
# # We reduce volume by one mesure first, then by two measures, # (n**2 + n) / 2
# # then three, and so on. total_measures_count = (steps**2 + steps) / 2
#
# # The sum of the arithmetic sequence 1, 2, 3, ..n is measures_to_reduce_by = 0
# # (n**2 + n) / 2 for i in range(1, steps + 1):
# total_measures_count = (steps**2 + steps) / 2 measures_to_reduce_by += i
# volume_factor = 1 - (
# measures_to_reduce_by = 0 measures_to_reduce_by / total_measures_count)
# for i in range(1, steps + 1): p.audio_set_volume(int(self.max_volume * volume_factor))
# measures_to_reduce_by += i sleep(sleep_time)
# volume_factor = 1 - (
# measures_to_reduce_by / total_measures_count) with lock:
# p.audio_set_volume(int(self.max_volume * volume_factor)) p.stop()
# sleep(sleep_time) log.debug(f"Releasing player {p=}")
# p.release()
# with lock:
# log.debug(f"music._fade(), stopping {p=}", True) def get_playtime(self) -> Optional[int]:
# """Return elapsed play time"""
# p.stop()
# log.debug(f"Releasing player {p=}", True) if not self.player:
# p.release() return None
#
# self.fading -= 1 return self.player.get_time()
#
# def get_playtime(self): def get_position(self) -> Optional[float]:
# """Return elapsed play time""" """Return current position"""
#
# with lock: if not self.player:
# if not self.player: return None
# return None return self.player.get_position()
#
# return self.player.get_time() def play(self, path: str,
# position: Optional[float] = None) -> Optional[int]:
# def get_position(self): """
# """Return current position""" Start playing the track at path.
#
# with lock: Log and return if path not found.
# log.debug("music.get_position", True) """
#
# print(f"get_position, {self.player=}") if not file_is_readable(path):
# if not self.player: log.error(f"play({path}): path not readable")
# return return None
# return self.player.get_position()
# status = -1
# def play(self, path): self.track_path = path
# """
# Start playing the track at path. self.player = self.VLC.media_player_new(path)
# if self.player:
# Log and return if path not found. self.player.audio_set_volume(self.max_volume)
# """ self.current_track_start_time = datetime.now()
# status = self.player.play()
# log.debug(f"music.play({path=})", True) if position:
# self.player.set_position(position)
# if not os.access(path, os.R_OK):
# log.error(f"play({path}): path not found") return status
# return
#
# self.track_path = path
#
# self.player = self.VLC.media_player_new(path)
# self.player.audio_set_volume(self.max_volume)
# log.debug(f"music.play({path=}), {self.player}", True)
# self.player.play()
# self.current_track_start_time = datetime.now()
#
# def playing(self):
# """
# Return True if currently playing a track, else False
#
# vlc.is_playing() returns True if track was faded out.
# get_position seems more reliable.
# """
#
# with lock:
# if self.player:
# if self.player.get_position() > 0 and self.player.is_playing():
# return True
#
# # We take a copy of the player when fading, so we could be
# # playing in a fade nowFalse
# return self.fading > 0
# #
# def set_position(self, ms): # def set_position(self, ms):
# """Set current play time in milliseconds from start""" # """Set current play time in milliseconds from start"""
@ -164,20 +139,17 @@
# self.max_volume = volume # self.max_volume = volume
# #
# self.player.audio_set_volume(volume) # self.player.audio_set_volume(volume)
#
# def stop(self): def stop(self) -> float:
# """Immediately stop playing""" """Immediately stop playing"""
#
# log.debug(f"music.stop(), {self.player=}", True) with lock:
# if not self.player:
# with lock: return 0.0
# if not self.player:
# return position = self.player.get_position()
# self.player.stop()
# position = self.player.get_position() self.player.release()
# self.player.stop() # Ensure we don't reference player after release
# log.debug(f"music.stop(): Releasing player {self.player=}", True) self.player = None
# self.player.release() return position
# # Ensure we don't reference player after release
# self.player = None
# return position

View File

@ -8,11 +8,11 @@ import sys
# import webbrowser # import webbrowser
# #
# #
# from datetime import datetime, timedelta from datetime import datetime, timedelta
# from typing import Callable, Dict, List, Optional, Tuple # from typing import Callable, Dict, List, Optional, Tuple
# #
# from PyQt5.QtCore import QDate, QEvent, QProcess, Qt, QTime, QTimer, QUrl # from PyQt5.QtCore import QDate, QProcess, Qt, QTime, QTimer, QUrl
from PyQt5.QtCore import Qt from PyQt5.QtCore import QEvent, Qt, QTimer
from PyQt5.QtGui import QColor from PyQt5.QtGui import QColor
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QApplication, QApplication,
@ -27,12 +27,12 @@ from PyQt5.QtWidgets import (
) )
# #
from dbconfig import engine, Session from dbconfig import engine, Session
# import helpers import helpers
# import music import music
# #
from models import ( from models import (
Base, Base,
# Playdates, Playdates,
PlaylistRows, PlaylistRows,
Playlists, Playlists,
Settings, Settings,
@ -41,14 +41,13 @@ from models import (
from playlists import PlaylistTab from playlists import PlaylistTab
from sqlalchemy.orm.exc import DetachedInstanceError from sqlalchemy.orm.exc import DetachedInstanceError
# from ui.dlg_search_database_ui import Ui_Dialog # from ui.dlg_search_database_ui import Ui_Dialog
from ui.dlg_SelectPlaylist_ui import Ui_dlgSelectPlaylist from ui.dlg_SelectPlaylist_ui import Ui_dlgSelectPlaylist # type: ignore
# from ui.downloadcsv_ui import Ui_DateSelect # from ui.downloadcsv_ui import Ui_DateSelect
from config import Config from config import Config
from ui.main_window_ui import Ui_MainWindow from ui.main_window_ui import Ui_MainWindow # type: ignore
# from utilities import create_track_from_file, update_db # from utilities import create_track_from_file, update_db
#
#
# log = logging.getLogger(Config.LOG_NAME)
class TrackData: class TrackData:
def __init__(self, track): def __init__(self, track):
self.id = track.id self.id = track.id
@ -67,12 +66,11 @@ class Window(QMainWindow, Ui_MainWindow):
super().__init__(parent) super().__init__(parent)
self.setupUi(self) self.setupUi(self)
# self.timer: QTimer = QTimer() self.timer: QTimer = QTimer()
# self.even_tick: bool = True self.even_tick: bool = True
# self.playing: bool = False self.playing: bool = False
# self.disable_play_next_controls()
# self.music: music.Music = music.Music()
# self.music: music.Music = music.Music()
self.current_track: Optional[TrackData] = None self.current_track: Optional[TrackData] = None
self.current_track_playlist_tab: Optional[PlaylistTab] = None self.current_track_playlist_tab: Optional[PlaylistTab] = None
self.next_track: Optional[TrackData] = None self.next_track: Optional[TrackData] = None
@ -86,16 +84,16 @@ class Window(QMainWindow, Ui_MainWindow):
# self.txtSearch = QLineEdit() # self.txtSearch = QLineEdit()
# self.statusbar.addWidget(self.txtSearch) # self.statusbar.addWidget(self.txtSearch)
# self.txtSearch.setHidden(True) # self.txtSearch.setHidden(True)
# self.hide_played_tracks = False self.hide_played_tracks = False
# #
self.splitter.setSizes([200, 200]) self.splitter.setSizes([200, 200])
self.visible_playlist_tab: Callable[[], PlaylistTab] = \ self.visible_playlist_tab: Callable[[], PlaylistTab] = \
self.tabPlaylist.currentWidget self.tabPlaylist.currentWidget
#
self._load_last_playlists() self._load_last_playlists()
# self.enable_play_next_controls() self.enable_play_next_controls()
# self.check_audacity() # self.check_audacity()
# self.timer.start(Config.TIMER_MS) self.timer.start(Config.TIMER_MS)
self.connect_signals_slots() self.connect_signals_slots()
# #
# def set_main_window_size(self) -> None: # def set_main_window_size(self) -> None:
@ -137,61 +135,65 @@ class Window(QMainWindow, Ui_MainWindow):
if self.visible_playlist_tab(): if self.visible_playlist_tab():
self.visible_playlist_tab().clear_selection() self.visible_playlist_tab().clear_selection()
#
# def closeEvent(self, event: QEvent) -> None: def closeEvent(self, event: QEvent) -> None:
# """Don't allow window to close when a track is playing""" """Handle attempt to close main window"""
#
# if self.music.playing(): # Don't allow window to close when a track is playing
# log.debug("closeEvent() ignored as music is playing") if self.music.player and self.music.player.is_playing():
# event.ignore() event.ignore()
# helpers.show_warning( helpers.show_warning(
# "Track playing", "Track playing",
# "Can't close application while track is playing") "Can't close application while track is playing")
# else: else:
# log.debug("closeEvent() accepted") with Session() as session:
# record = Settings.get_int_settings(
# with Session() as session: session, "mainwindow_height")
# record = Settings.get_int_settings( if record.f_int != self.height():
# session, "mainwindow_height") record.update(session, {'f_int': self.height()})
# if record.f_int != self.height():
# record.update(session, {'f_int': self.height()}) record = Settings.get_int_settings(session, "mainwindow_width")
# if record.f_int != self.width():
# record = Settings.get_int_settings(session, "mainwindow_width") record.update(session, {'f_int': self.width()})
# if record.f_int != self.width():
# record.update(session, {'f_int': self.width()}) record = Settings.get_int_settings(session, "mainwindow_x")
# if record.f_int != self.x():
# record = Settings.get_int_settings(session, "mainwindow_x") record.update(session, {'f_int': self.x()})
# if record.f_int != self.x():
# record.update(session, {'f_int': self.x()}) record = Settings.get_int_settings(session, "mainwindow_y")
# if record.f_int != self.y():
# record = Settings.get_int_settings(session, "mainwindow_y") record.update(session, {'f_int': self.y()})
# if record.f_int != self.y():
# record.update(session, {'f_int': self.y()}) # Save splitter settings
# splitter_sizes = self.splitter.sizes()
# # Find a playlist tab (as opposed to an info tab) and assert len(splitter_sizes) == 2
# # save column widths splitter_top, splitter_bottom = splitter_sizes
# if self.current_track_playlist_tab:
# self.current_track_playlist_tab.close() record = Settings.get_int_settings(session, "splitter_top")
# elif self.next_track_playlist_tab: if record.f_int != splitter_top:
# self.next_track_playlist_tab.close() record.update(session, {'f_int': splitter_top})
#
# event.accept() record = Settings.get_int_settings(session, "splitter_bottom")
if record.f_int != splitter_bottom:
record.update(session, {'f_int': splitter_bottom})
event.accept()
def connect_signals_slots(self) -> None: def connect_signals_slots(self) -> None:
# self.actionAdd_note.triggered.connect(self.create_note) # self.actionAdd_note.triggered.connect(self.create_note)
self.action_Clear_selection.triggered.connect(self.clear_selection) self.action_Clear_selection.triggered.connect(self.clear_selection)
# self.actionClosePlaylist.triggered.connect(self.close_playlist_tab) # self.actionClosePlaylist.triggered.connect(self.close_playlist_tab)
# self.actionDownload_CSV_of_played_tracks.triggered.connect( # self.actionDownload_CSV_of_played_tracks.triggered.connect(
# self.download_played_tracks) # self.download_played_tracks)
# self.actionEnable_controls.triggered.connect( self.actionEnable_controls.triggered.connect(
# self.enable_play_next_controls) self.enable_play_next_controls)
# self.actionExport_playlist.triggered.connect(self.export_playlist_tab) # self.actionExport_playlist.triggered.connect(self.export_playlist_tab)
# self.actionImport.triggered.connect(self.import_track) # self.actionImport.triggered.connect(self.import_track)
# self.actionFade.triggered.connect(self.fade) self.actionFade.triggered.connect(self.fade)
# self.actionMoveSelected.triggered.connect(self.move_selected) # self.actionMoveSelected.triggered.connect(self.move_selected)
# self.actionNewPlaylist.triggered.connect(self.create_playlist) # self.actionNewPlaylist.triggered.connect(self.create_playlist)
# self.actionOpenPlaylist.triggered.connect(self.open_playlist) # self.actionOpenPlaylist.triggered.connect(self.open_playlist)
# self.actionPlay_next.triggered.connect(self.play_next) self.actionPlay_next.triggered.connect(self.play_next)
# self.actionSearch.triggered.connect(self.search_playlist) # self.actionSearch.triggered.connect(self.search_playlist)
# self.actionSearch_database.triggered.connect(self.search_database) # self.actionSearch_database.triggered.connect(self.search_database)
# self.actionSelect_next_track.triggered.connect(self.select_next_row) # self.actionSelect_next_track.triggered.connect(self.select_next_row)
@ -200,25 +202,19 @@ class Window(QMainWindow, Ui_MainWindow):
# self.select_previous_row) # self.select_previous_row)
# self.actionSelect_unplayed_tracks.triggered.connect( # self.actionSelect_unplayed_tracks.triggered.connect(
# self.select_unplayed) # self.select_unplayed)
# self.actionSetNext.triggered.connect( self.actionSetNext.triggered.connect(
# lambda: self.tabPlaylist.currentWidget().set_selected_as_next()) lambda: self.tabPlaylist.currentWidget().set_selected_as_next())
# self.actionSkip_next.triggered.connect(self.play_next) # self.actionSkip_next.triggered.connect(self.play_next)
# self.actionStop.triggered.connect(self.stop) self.actionStop.triggered.connect(self.stop)
# # self.btnAddNote.clicked.connect(self.create_note)
# # self.btnDatabase.clicked.connect(self.search_database)
# self.btnDrop3db.clicked.connect(self.drop3db) # self.btnDrop3db.clicked.connect(self.drop3db)
# self.btnHidePlayed.clicked.connect(self.hide_played) # self.btnHidePlayed.clicked.connect(self.hide_played)
# self.btnFade.clicked.connect(self.fade) self.btnFade.clicked.connect(self.fade)
# # self.btnPlay.clicked.connect(self.play_next) self.btnStop.clicked.connect(self.stop)
# # self.btnSetNext.clicked.connect(
# # lambda: self.tabPlaylist.currentWidget().set_selected_as_next())
# # self.btnSongInfo.clicked.connect(self.song_info_search)
# self.btnStop.clicked.connect(self.stop)
# self.tabPlaylist.tabCloseRequested.connect(self.close_tab) # self.tabPlaylist.tabCloseRequested.connect(self.close_tab)
# self.txtSearch.returnPressed.connect(self.search_playlist_return) # self.txtSearch.returnPressed.connect(self.search_playlist_return)
# self.txtSearch.textChanged.connect(self.search_playlist_update) # self.txtSearch.textChanged.connect(self.search_playlist_update)
# #
# self.timer.timeout.connect(self.tick) self.timer.timeout.connect(self.tick)
# #
# def create_playlist(self) -> None: # def create_playlist(self) -> None:
# """Create new playlist""" # """Create new playlist"""
@ -275,19 +271,18 @@ class Window(QMainWindow, Ui_MainWindow):
add tab to display. add tab to display.
""" """
playlist_tab: PlaylistTab = PlaylistTab( playlist_tab = PlaylistTab(
musicmuster=self, session=session, playlist_id=playlist.id) musicmuster=self, session=session, playlist_id=playlist.id)
idx: int = self.tabPlaylist.addTab(playlist_tab, playlist.name) idx = self.tabPlaylist.addTab(playlist_tab, playlist.name)
self.tabPlaylist.setCurrentIndex(idx) self.tabPlaylist.setCurrentIndex(idx)
#
# def disable_play_next_controls(self) -> None: def disable_play_next_controls(self) -> None:
# """ """
# Disable "play next" keyboard controls Disable "play next" keyboard controls
# """ """
#
# log.debug("disable_play_next_controls()") self.actionPlay_next.setEnabled(False)
# self.actionPlay_next.setEnabled(False) self.statusbar.showMessage("Play controls: Disabled", 0)
# self.statusbar.showMessage("Play controls: Disabled", 0)
# #
# def download_played_tracks(self) -> None: # def download_played_tracks(self) -> None:
# """Download a CSV of played tracks""" # """Download a CSV of played tracks"""
@ -322,62 +317,61 @@ class Window(QMainWindow, Ui_MainWindow):
# self.music.set_volume(Config.VOLUME_VLC_DROP3db, set_default=False) # self.music.set_volume(Config.VOLUME_VLC_DROP3db, set_default=False)
# else: # else:
# self.music.set_volume(Config.VOLUME_VLC_DEFAULT, set_default=False) # self.music.set_volume(Config.VOLUME_VLC_DEFAULT, set_default=False)
#
# def enable_play_next_controls(self) -> None: def enable_play_next_controls(self) -> None:
# """ """
# Enable "play next" keyboard controls Enable "play next" keyboard controls
# """ """
#
# log.debug("enable_play_next_controls()") self.actionPlay_next.setEnabled(True)
# self.actionPlay_next.setEnabled(True) self.statusbar.showMessage("Play controls: Enabled", 0)
# self.statusbar.showMessage("Play controls: Enabled", 0)
# def end_of_track_actions(self) -> None:
# def end_of_track_actions(self) -> None: """
# """ Clean up after track played
# Clean up after track played
# Actions required:
# Actions required: - Set flag to say we're not playing a track
# - Set flag to say we're not playing a track - Reset current track
# - Reset current track - Tell playlist_tab track has finished
# - Tell playlist_tab track has finished - Reset current playlist_tab
# - Reset current playlist_tab - Reset clocks
# - Reset clocks - Update headers
# - Update headers - Enable controls
# - Enable controls """
# """
# # Set flag to say we're not playing a track so that tick()
# # Set flag to say we're not playing a track so that tick() # doesn't see player=None and kick off end-of-track actions
# # doesn't see player=None and kick off end-of-track actions self.playing = False
# self.playing = False
# # Reset current track
# # Reset current track if self.current_track:
# if self.current_track: self.previous_track = self.current_track
# self.previous_track = self.current_track self.current_track = None
# self.current_track = None
# # Tell playlist_tab track has finished and
# # Tell playlist_tab track has finished and # reset current playlist_tab
# # reset current playlist_tab if self.current_track_playlist_tab:
# if self.current_track_playlist_tab: self.current_track_playlist_tab.play_stopped()
# self.current_track_playlist_tab.play_stopped() self.current_track_playlist_tab = None
# self.current_track_playlist_tab = None
# # Reset clocks
# # Reset clocks self.frame_fade.setStyleSheet("")
# self.frame_fade.setStyleSheet("") self.frame_silent.setStyleSheet("")
# self.frame_silent.setStyleSheet("") self.label_elapsed_timer.setText("00:00")
# self.label_elapsed_timer.setText("00:00") self.label_end_timer.setText("00:00")
# self.label_end_timer.setText("00:00") self.label_fade_length.setText("0:00")
# self.label_fade_length.setText("0:00") self.label_fade_timer.setText("00:00")
# self.label_fade_timer.setText("00:00") self.label_silent_timer.setText("00:00")
# self.label_silent_timer.setText("00:00") self.label_track_length.setText("0:00")
# self.label_track_length.setText("0:00") self.label_start_time.setText("00:00:00")
# self.label_start_time.setText("00:00:00") self.label_end_time.setText("00:00:00")
# self.label_end_time.setText("00:00:00")
# # Update headers
# # Update headers self.update_headers()
# self.update_headers()
# # Enable controls
# # Enable controls self.enable_play_next_controls()
# self.enable_play_next_controls()
# #
# def export_playlist_tab(self) -> None: # def export_playlist_tab(self) -> None:
# """Export the current playlist to an m3u file""" # """Export the current playlist to an m3u file"""
@ -414,13 +408,11 @@ class Window(QMainWindow, Ui_MainWindow):
# f"{track.path}" # f"{track.path}"
# "\n" # "\n"
# ) # )
#
# def fade(self) -> None: def fade(self) -> None:
# """Fade currently playing track""" """Fade currently playing track"""
#
# log.debug("musicmuster:fade()", True) self.stop_playing(fade=True)
#
# self.stop_playing(fade=True)
# #
# def hide_played(self): # def hide_played(self):
# """Toggle hide played tracks""" # """Toggle hide played tracks"""
@ -553,103 +545,91 @@ class Window(QMainWindow, Ui_MainWindow):
if destination_visible_playlist_tab: if destination_visible_playlist_tab:
destination_visible_playlist_tab.populate( destination_visible_playlist_tab.populate(
session, dlg.playlist.id) session, dlg.playlist.id)
#
# def play_next(self) -> None: def play_next(self) -> None:
# """ """
# Play next track. Play next track.
#
# Actions required: Actions required:
# - If there is no next track set, return. - If there is no next track set, return.
# - If there's currently a track playing, fade it. - If there's currently a track playing, fade it.
# - Move next track to current track. - Move next track to current track.
# - Update record of current track playlist_tab - Ensure playlist tabs are the correct colour
# - If current track on different playlist_tab to last, reset - Restore volume if -3dB active
# last track playlist_tab colour - Play (new) current track.
# - Set current track playlist_tab colour - Tell database to record it as played
# - Restore volume if -3dB active - Tell playlist track is now playing
# - Play (new) current track. - Note that track is now playing
# - Tell database to record it as played - Disable play next controls
# - Tell playlist track is now playing - Update headers
# - Disable play next controls - Update clocks
# - Update headers """
# - Update clocks
# """ # If there is no next track set, return.
# if not self.next_track:
# log.debug( log.debug("musicmuster.play_next(): no next track selected")
# "musicmuster.play_next(), " return
# f"next_track={self.next_track.title if self.next_track else None} "
# "current_track=" with Session() as session:
# f"{self.current_track.title if self.current_track else None}", # If there's currently a track playing, fade it.
# True self.stop_playing(fade=True)
# )
# # Move next track to current track.
# # If there is no next track set, return. self.current_track = self.next_track
# if not self.next_track: self.next_track = None
# log.debug("musicmuster.play_next(): no next track selected", True)
# return # Ensure playlist tabs are the correct colour
# # If current track on different playlist_tab to last, reset
# with Session() as session: # last track playlist_tab colour
# # If there's currently a track playing, fade it. if self.current_track_playlist_tab != self.next_track_playlist_tab:
# self.stop_playing(fade=True) self.set_tab_colour(self.current_track_playlist_tab,
# QColor(Config.COLOUR_NORMAL_TAB))
# # Move next track to current track. # # Update record of current track playlist_tab
# self.current_track = self.next_track self.current_track_playlist_tab = self.next_track_playlist_tab
# self.next_track = None self.next_track_playlist_tab = None
# # Set current track playlist_tab colour
# # If current track on different playlist_tab to last, reset self.set_tab_colour(self.current_track_playlist_tab,
# # last track playlist_tab colour QColor(Config.COLOUR_CURRENT_TAB))
# # Set current track playlist_tab colour
# if self.current_track_playlist_tab != self.next_track_playlist_tab: # Restore volume if -3dB active
# self.set_tab_colour(self.current_track_playlist_tab, if self.btnDrop3db.isChecked():
# QColor(Config.COLOUR_NORMAL_TAB)) self.btnDrop3db.setChecked(False)
#
# # Update record of current track playlist_tab # Play (new) current track
# self.current_track_playlist_tab = self.next_track_playlist_tab start_at = datetime.now()
# self.next_track_playlist_tab = None self.music.play(self.current_track.path)
#
# # Set current track playlist_tab colour # Tell database to record it as played
# self.set_tab_colour(self.current_track_playlist_tab, Playdates(session, self.current_track.id)
# QColor(Config.COLOUR_CURRENT_TAB))
# # Tell playlist track is now playing
# # Restore volume if -3dB active self.current_track_playlist_tab.play_started(session)
# if self.btnDrop3db.isChecked():
# self.btnDrop3db.setChecked(False) # Note that track is now playing
# self.playing = True
# # Play (new) current track
# start_at = datetime.now() # Disable play next controls
# self.music.play(self.current_track.path) self.disable_play_next_controls()
#
# # Tell database to record it as played # Update headers
# Playdates(session, self.current_track.id) self.update_headers()
#
# # Set last_played date # Update clocks
# Tracks.update_lastplayed(session, self.current_track.id) self.label_track_length.setText(
# helpers.ms_to_mmss(self.current_track.duration)
# # Tell playlist track is now playing )
# self.current_track_playlist_tab.play_started(session) fade_at = self.current_track.fade_at
# silence_at = self.current_track.silence_at
# # Disable play next controls length = self.current_track.duration
# self.disable_play_next_controls() self.label_fade_length.setText(
# helpers.ms_to_mmss(silence_at - fade_at))
# # Update headers self.label_start_time.setText(
# self.update_headers() start_at.strftime(Config.TRACK_TIME_FORMAT))
# end_at = start_at + timedelta(
# # Update clocks milliseconds=self.current_track.duration)
# self.label_track_length.setText( self.label_end_time.setText(
# helpers.ms_to_mmss(self.current_track.duration) end_at.strftime(Config.TRACK_TIME_FORMAT))
# )
# fade_at = self.current_track.fade_at
# silence_at = self.current_track.silence_at
# length = self.current_track.duration
# self.label_fade_length.setText(
# helpers.ms_to_mmss(silence_at - fade_at))
# self.label_start_time.setText(
# start_at.strftime(Config.TRACK_TIME_FORMAT))
# end_at = start_at + timedelta(
# milliseconds=self.current_track.duration)
# self.label_end_time.setText(
# end_at.strftime(Config.TRACK_TIME_FORMAT))
#
# def search_database(self) -> None: # def search_database(self) -> None:
# """Show dialog box to select and cue track from database""" # """Show dialog box to select and cue track from database"""
# #
@ -709,12 +689,12 @@ class Window(QMainWindow, Ui_MainWindow):
# self.visible_playlist_tab().select_unplayed_tracks() # self.visible_playlist_tab().select_unplayed_tracks()
def set_tab_colour(self, widget: PlaylistTab, colour: QColor) -> None: def set_tab_colour(self, widget: PlaylistTab, colour: QColor) -> None:
""" """
Find the tab containing the widget and set the text colour Find the tab containing the widget and set the text colour
""" """
idx = self.tabPlaylist.indexOf(widget) idx = self.tabPlaylist.indexOf(widget)
self.tabPlaylist.tabBar().setTabTextColor(idx, colour) self.tabPlaylist.tabBar().setTabTextColor(idx, colour)
# #
# def song_info_search(self) -> None: # def song_info_search(self) -> None:
# """ # """
@ -736,54 +716,48 @@ class Window(QMainWindow, Ui_MainWindow):
# txt = urllib.parse.quote_plus(title) # txt = urllib.parse.quote_plus(title)
# url = Config.TAB_URL % txt # url = Config.TAB_URL % txt
# webbrowser.open(url, new=2) # webbrowser.open(url, new=2)
#
# def stop(self) -> None:
# """Stop playing immediately"""
#
# log.debug("musicmuster.stop()")
#
# self.stop_playing(fade=False)
#
# def stop_playing(self, fade=True) -> None:
# """
# Stop playing current track
#
# Actions required:
# - Return if not playing
# - Stop/fade track
# - Reset playlist_tab colour
# - Run end-of-track actions
# """
#
# log.debug(f"musicmuster.stop_playing({fade=})", True)
#
# # Return if not playing
# if not self.playing:
# log.debug("musicmuster.stop_playing(): not playing", True)
# return
#
# # Stop/fade track
# self.previous_track_position = self.music.get_position()
# if fade:
# log.debug("musicmuster.stop_playing(): fading music", True)
# self.music.fade()
# else:
# log.debug("musicmuster.stop_playing(): stopping music", True)
# self.music.stop()
#
# # Reset playlist_tab colour
# if self.current_track_playlist_tab == self.next_track_playlist_tab:
# self.set_tab_colour(self.current_track_playlist_tab,
# QColor(Config.COLOUR_NEXT_TAB))
# else:
# self.set_tab_colour(self.current_track_playlist_tab,
# QColor(Config.COLOUR_NORMAL_TAB))
#
# # Run end-of-track actions
# self.end_of_track_actions()
def this_is_the_next_track(self, playlist_tab: PlaylistTab, def stop(self) -> None:
track: Tracks, session) -> None: """Stop playing immediately"""
self.stop_playing(fade=False)
def stop_playing(self, fade=True) -> None:
"""
Stop playing current track
Actions required:
- Return if not playing
- Stop/fade track
- Reset playlist_tab colour
- Run end-of-track actions
"""
# Return if not playing
if not self.playing:
return
# Stop/fade track
self.previous_track_position = self.music.get_position()
if fade:
self.music.fade()
else:
self.music.stop()
# Reset playlist_tab colour
if self.current_track_playlist_tab == self.next_track_playlist_tab:
self.set_tab_colour(self.current_track_playlist_tab,
QColor(Config.COLOUR_NEXT_TAB))
else:
self.set_tab_colour(self.current_track_playlist_tab,
QColor(Config.COLOUR_NORMAL_TAB))
# Run end-of-track actions
self.end_of_track_actions()
def this_is_the_next_track(self, session: Session,
playlist_tab: PlaylistTab,
track: Tracks) -> None:
""" """
This is notification from a playlist tab that it holds the next This is notification from a playlist tab that it holds the next
track to be played. track to be played.
@ -832,85 +806,81 @@ class Window(QMainWindow, Ui_MainWindow):
# Populate 'info' tabs # Populate 'info' tabs
self.tabInfolist.open_tab(track.title) self.tabInfolist.open_tab(track.title)
# def tick(self) -> None: def tick(self) -> None:
# """ """
# Carry out clock tick actions. Carry out clock tick actions.
#
# The Time of Day clock is updated every tick (500ms). The Time of Day clock is updated every tick (500ms).
#
# All other timers are updated every second. As the timers have a All other timers are updated every second. As the timer displays
# one-second resolution, updating every 500ms can result in some have a one-second resolution, updating every 500ms can result in
# timers updating and then, 500ms later, other timers updating. That some timers updating and then, 500ms later, other timers
# looks odd. updating. That looks odd.
#
# Actions required: Actions required:
# - Update TOD clock - Update TOD clock
# - If track is playing, update track clocks time and colours - If track is playing:
# - Else: run stop_track update track clocks time and colours
# """ - Else:
# run stop_track
# # Update TOD clock """
# self.lblTOD.setText(datetime.now().strftime(Config.TOD_TIME_FORMAT))
# # Update TOD clock
# self.even_tick = not self.even_tick self.lblTOD.setText(datetime.now().strftime(Config.TOD_TIME_FORMAT))
# if not self.even_tick:
# return self.even_tick = not self.even_tick
# if not self.even_tick:
# # If track is playing, update track clocks time and colours return
# if self.music.player and self.music.playing(): if not self.playing:
# self.playing = True return
# playtime: int = self.music.get_playtime()
# time_to_fade: int = (self.current_track.fade_at - playtime) # If track is playing, update track clocks time and colours
# time_to_silence: int = ( if self.music.player and self.music.player.is_playing():
# self.current_track.silence_at - playtime) playtime = self.music.get_playtime()
# time_to_end: int = (self.current_track.duration - playtime) time_to_fade = (self.current_track.fade_at - playtime)
# time_to_silence = (
# # Elapsed time self.current_track.silence_at - playtime)
# if time_to_end < 500: time_to_end = (self.current_track.duration - playtime)
# self.label_elapsed_timer.setText(
# helpers.ms_to_mmss(playtime) # Elapsed time
# ) self.label_elapsed_timer.setText(helpers.ms_to_mmss(playtime))
# else:
# self.label_elapsed_timer.setText( # Time to fade
# helpers.ms_to_mmss(playtime) self.label_fade_timer.setText(helpers.ms_to_mmss(time_to_fade))
# )
# # If silent in the next 5 seconds, put warning colour on
# # Time to fade # time to silence box and enable play controls
# self.label_fade_timer.setText(helpers.ms_to_mmss(time_to_fade)) if time_to_silence <= 5500:
# self.frame_silent.setStyleSheet(
# # If silent in the next 5 seconds, put warning colour on f"background: {Config.COLOUR_ENDING_TIMER}"
# # time to silence box and enable play controls )
# if time_to_silence <= 5500: self.enable_play_next_controls()
# self.frame_silent.setStyleSheet( # Set warning colour on time to silence box when fade starts
# f"background: {Config.COLOUR_ENDING_TIMER}" elif time_to_fade <= 500:
# ) self.frame_silent.setStyleSheet(
# self.enable_play_next_controls() f"background: {Config.COLOUR_WARNING_TIMER}"
# # Set warning colour on time to silence box when fade starts )
# elif time_to_fade <= 500: # Five seconds before fade starts, set warning colour on
# self.frame_silent.setStyleSheet( # time to silence box and enable play controls
# f"background: {Config.COLOUR_WARNING_TIMER}" elif time_to_fade <= 5500:
# ) self.frame_fade.setStyleSheet(
# # Five seconds before fade starts, set warning colour on f"background: {Config.COLOUR_WARNING_TIMER}"
# # time to silence box and enable play controls )
# elif time_to_fade <= 5500: self.enable_play_next_controls()
# self.frame_fade.setStyleSheet( else:
# f"background: {Config.COLOUR_WARNING_TIMER}" self.frame_silent.setStyleSheet("")
# ) self.frame_fade.setStyleSheet("")
# self.enable_play_next_controls()
# else: self.label_silent_timer.setText(
# self.frame_silent.setStyleSheet("") helpers.ms_to_mmss(time_to_silence)
# self.frame_fade.setStyleSheet("") )
#
# self.label_silent_timer.setText( # Time to end
# helpers.ms_to_mmss(time_to_silence) self.label_end_timer.setText(helpers.ms_to_mmss(time_to_end))
# )
# else:
# # Time to end if self.playing:
# self.label_end_timer.setText(helpers.ms_to_mmss(time_to_end)) self.stop_playing()
#
# else:
# if self.playing:
# self.stop_playing()
def update_headers(self) -> None: def update_headers(self) -> None:
""" """
@ -919,23 +889,21 @@ class Window(QMainWindow, Ui_MainWindow):
try: try:
self.hdrPreviousTrack.setText( self.hdrPreviousTrack.setText(
f"{self.previous_track.title} - {self.previous_track.artist}" f"{self.previous_track.title} - {self.previous_track.artist}")
) except AttributeError:
except (AttributeError, DetachedInstanceError):
self.hdrPreviousTrack.setText("") self.hdrPreviousTrack.setText("")
try: try:
self.hdrCurrentTrack.setText( self.hdrCurrentTrack.setText(
f"{self.current_track.title} - {self.current_track.artist}" f"{self.current_track.title} - {self.current_track.artist}")
) except AttributeError:
except (AttributeError, DetachedInstanceError):
self.hdrCurrentTrack.setText("") self.hdrCurrentTrack.setText("")
try: try:
self.hdrNextTrack.setText( self.hdrNextTrack.setText(
f"{self.next_track.title} - {self.next_track.artist}" f"{self.next_track.title} - {self.next_track.artist}"
) )
except (AttributeError, DetachedInstanceError): except AttributeError:
self.hdrNextTrack.setText("") self.hdrNextTrack.setText("")
# #
# #

View File

@ -1,15 +1,18 @@
from collections import namedtuple import re
import subprocess
import threading
from collections import namedtuple
from datetime import datetime, timedelta
from typing import List, Optional from typing import List, Optional
from PyQt5 import QtCore
from PyQt5.QtCore import Qt from PyQt5.QtCore import QEvent, Qt, pyqtSignal
from PyQt5.QtGui import ( from PyQt5.QtGui import (
QBrush, QBrush,
QColor, QColor,
QFont, QFont,
QDropEvent QDropEvent
) )
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QAbstractItemView, QAbstractItemView,
# QApplication, # QApplication,
@ -22,17 +25,13 @@ from PyQt5.QtWidgets import (
QTableWidget, QTableWidget,
QTableWidgetItem, QTableWidgetItem,
) )
#
import helpers
# import os
import re
import subprocess
import threading
#
from config import Config from config import Config
from datetime import datetime # , timedelta from dbconfig import Session
from helpers import ( from helpers import (
file_is_readable,
get_relative_date, get_relative_date,
ms_to_mmss,
open_in_audacity open_in_audacity
) )
from log import log from log import log
@ -44,7 +43,7 @@ from models import (
Tracks, Tracks,
NoteColours NoteColours
) )
from dbconfig import Session
start_time_re = re.compile(r"@\d\d:\d\d:\d\d") start_time_re = re.compile(r"@\d\d:\d\d:\d\d")
@ -54,7 +53,6 @@ class RowMeta:
UNREADABLE = 2 UNREADABLE = 2
NEXT = 3 NEXT = 3
CURRENT = 4 CURRENT = 4
PLAYED = 5
# Columns # Columns
@ -100,21 +98,21 @@ class PlaylistTab(QTableWidget):
def __init__(self, musicmuster: QMainWindow, session: Session, def __init__(self, musicmuster: QMainWindow, session: Session,
playlist_id: int, *args, **kwargs) -> None: playlist_id: int, *args, **kwargs) -> None:
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.musicmuster: QMainWindow = musicmuster self.musicmuster = musicmuster
self.playlist_id: int = playlist_id self.playlist_id = playlist_id
self.menu: Optional[QMenu] = None self.menu: Optional[QMenu] = None
# self.current_track_start_time: Optional[datetime] = None self.current_track_start_time: Optional[datetime] = None
# #
# # Don't select text on edit # # Don't select text on edit
# self.setItemDelegate(NoSelectDelegate(self)) # self.setItemDelegate(NoSelectDelegate(self))
# #
# Set up widget # Set up widget
# self.setEditTriggers(QtWidgets.QAbstractItemView.AllEditTriggers) # self.setEditTriggers(QAbstractItemView.AllEditTriggers)
self.setAlternatingRowColors(True) self.setAlternatingRowColors(True)
self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) self.setSelectionMode(QAbstractItemView.ExtendedSelection)
self.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) self.setSelectionBehavior(QAbstractItemView.SelectRows)
self.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel) self.setVerticalScrollMode(QAbstractItemView.ScrollPerPixel)
self.setRowCount(0) self.setRowCount(0)
self.setColumnCount(len(columns)) self.setColumnCount(len(columns))
@ -138,7 +136,7 @@ class PlaylistTab(QTableWidget):
self.setDragEnabled(False) self.setDragEnabled(False)
# This property defines how the widget shows a context menu # This property defines how the widget shows a context menu
self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.setContextMenuPolicy(Qt.CustomContextMenu)
# This signal is emitted when the widget's contextMenuPolicy is # This signal is emitted when the widget's contextMenuPolicy is
# Qt::CustomContextMenu, and the user has requested a context # Qt::CustomContextMenu, and the user has requested a context
# menu on the widget. # menu on the widget.
@ -247,8 +245,8 @@ class PlaylistTab(QTableWidget):
def eventFilter(self, source, event): def eventFilter(self, source, event):
"""Used to process context (right-click) menu, which is defined here""" """Used to process context (right-click) menu, which is defined here"""
if (event.type() == QtCore.QEvent.MouseButtonPress and # noqa W504 if (event.type() == QEvent.MouseButtonPress and # noqa W504
event.buttons() == QtCore.Qt.RightButton and # noqa W504 event.buttons() == Qt.RightButton and # noqa W504
source is self.viewport()): source is self.viewport()):
item = self.itemAt(event.pos()) item = self.itemAt(event.pos())
if item is not None: if item is not None:
@ -395,14 +393,14 @@ class PlaylistTab(QTableWidget):
# note: Notes = Notes( # note: Notes = Notes(
# session, self.playlist_id, row, dlg.textValue()) # session, self.playlist_id, row, dlg.textValue())
# self._insert_note(session, note, row, True) # checked # self._insert_note(session, note, row, True) # checked
#
# def get_selected_row(self) -> Optional[int]: def get_selected_row(self) -> Optional[int]:
# """Return row number of first selected row, or None if none selected""" """Return row number of first selected row, or None if none selected"""
#
# if not self.selectionModel().hasSelection(): if not self.selectionModel().hasSelection():
# return None return None
# else: else:
# return self.selectionModel().selectedRows()[0].row() return self.selectionModel().selectedRows()[0].row()
# #
# def get_selected_rows(self) -> List[int]: # def get_selected_rows(self) -> List[int]:
# """Return a sorted list of selected row numbers""" # """Return a sorted list of selected row numbers"""
@ -415,7 +413,7 @@ class PlaylistTab(QTableWidget):
# #
# if self.selectionModel().hasSelection(): # if self.selectionModel().hasSelection():
# row = self.currentRow() # row = self.currentRow()
# return self.item(row, self.COL_TITLE).text() # return self.item(row, FIXUP.COL_TITLE).text()
# else: # else:
# return None # return None
@ -459,7 +457,7 @@ class PlaylistTab(QTableWidget):
self.setItem(row, columns['artist'].idx, artist_item) self.setItem(row, columns['artist'].idx, artist_item)
duration_item = QTableWidgetItem( duration_item = QTableWidgetItem(
helpers.ms_to_mmss(row_data.track.duration)) ms_to_mmss(row_data.track.duration))
self.setItem(row, columns['duration'].idx, duration_item) self.setItem(row, columns['duration'].idx, duration_item)
self._set_row_duration(row, row_data.track.duration) self._set_row_duration(row, row_data.track.duration)
@ -480,7 +478,7 @@ class PlaylistTab(QTableWidget):
self.setItem(row, columns['lastplayed'].idx, last_played_item) self.setItem(row, columns['lastplayed'].idx, last_played_item)
# Mark track if file is unreadable # Mark track if file is unreadable
if not helpers.file_is_readable(row_data.track.path): if not file_is_readable(row_data.track.path):
self._set_unreadable_row(row) self._set_unreadable_row(row)
else: else:
@ -524,48 +522,48 @@ class PlaylistTab(QTableWidget):
# item: QTableWidgetItem = QTableWidgetItem() # item: QTableWidgetItem = QTableWidgetItem()
# # Add row metadata # # Add row metadata
# item.setData(self.ROW_FLAGS, 0) # item.setData(self.ROW_FLAGS, 0)
# self.setItem(row, self.COL_USERDATA, item) # self.setItem(row, FIXUP.COL_USERDATA, item)
# #
# # Add track details to columns # # Add track details to columns
# mss_item: QTableWidgetItem = QTableWidgetItem(str(track.start_gap)) # mss_item: QTableWidgetItem = QTableWidgetItem(str(track.start_gap))
# if track.start_gap and track.start_gap >= 500: # if track.start_gap and track.start_gap >= 500:
# mss_item.setBackground(QColor(Config.COLOUR_LONG_START)) # mss_item.setBackground(QColor(Config.COLOUR_LONG_START))
# self.setItem(row, self.COL_MSS, mss_item) # self.setItem(row, FIXUP.COL_MSS, mss_item)
# #
# title_item: QTableWidgetItem = QTableWidgetItem(track.title) # title_item: QTableWidgetItem = QTableWidgetItem(track.title)
# self.setItem(row, self.COL_TITLE, title_item) # self.setItem(row, FIXUP.COL_TITLE, title_item)
# #
# artist_item: QTableWidgetItem = QTableWidgetItem(track.artist) # artist_item: QTableWidgetItem = QTableWidgetItem(track.artist)
# self.setItem(row, self.COL_ARTIST, artist_item) # self.setItem(row, FIXUP.COL_ARTIST, artist_item)
# #
# duration_item: QTableWidgetItem = QTableWidgetItem( # duration_item: QTableWidgetItem = QTableWidgetItem(
# helpers.ms_to_mmss(track.duration) # ms_to_mmss(track.duration)
# ) # )
# self._set_row_duration(row, track.duration) # self._set_row_duration(row, track.duration)
# self.setItem(row, self.COL_DURATION, duration_item) # self.setItem(row, FIXUP.COL_DURATION, duration_item)
# #
# last_playtime: Optional[datetime] = Playdates.last_played( # last_playtime: Optional[datetime] = Playdates.last_played(
# session, track.id) # session, track.id)
# last_played_str: str = get_relative_date(last_playtime) # last_played_str: str = get_relative_date(last_playtime)
# last_played_item: QTableWidgetItem = QTableWidgetItem(last_played_str) # last_played_item: QTableWidgetItem = QTableWidgetItem(last_played_str)
# self.setItem(row, self.COL_LAST_PLAYED, last_played_item) # self.setItem(row, FIXUP.COL_LAST_PLAYED, last_played_item)
# #
# row_note: Optional[str] = "Play text" # row_note: Optional[str] = "Play text"
# row_note_item: QTableWidgetItem = QTableWidgetItem(row_note) # row_note_item: QTableWidgetItem = QTableWidgetItem(row_note)
# self.setItem(row, self.COL_ROW_NOTES, row_note_item) # self.setItem(row, FIXUP.COL_ROW_NOTES, row_note_item)
# #
# # Add empty start and stop time because background # # Add empty start and stop time because background
# # colour won't be set for columns without items # # colour won't be set for columns without items
# start_item: QTableWidgetItem = QTableWidgetItem() # start_item: QTableWidgetItem = QTableWidgetItem()
# self.setItem(row, self.COL_START_TIME, start_item) # self.setItem(row, FIXUP.COL_START_TIME, start_item)
# stop_item: QTableWidgetItem = QTableWidgetItem() # stop_item: QTableWidgetItem = QTableWidgetItem()
# self.setItem(row, self.COL_END_TIME, stop_item) # self.setItem(row, FIXUP.COL_END_TIME, stop_item)
# #
# # Attach track.id object to row # # Attach track.id object to row
# self._set_row_content(row, track.id) # self._set_row_content(row, track.id)
# #
# # Mark track if file is unreadable # # Mark track if file is unreadable
# if not helpers.file_is_readable(track.path): # if not file_is_readable(track.path):
# self._set_unreadable_row(row) # self._set_unreadable_row(row)
# # Scroll to new row # # Scroll to new row
# self.scrollToItem(title_item, QAbstractItemView.PositionAtCenter) # self.scrollToItem(title_item, QAbstractItemView.PositionAtCenter)
@ -618,62 +616,60 @@ class PlaylistTab(QTableWidget):
# #
# self.save_playlist(session) # self.save_playlist(session)
# self.update_display(session) # self.update_display(session)
#
# def play_started(self, session: Session) -> None: def play_started(self, session: Session) -> None:
# """ """
# Notification from musicmuster that track has started playing. Notification from musicmuster that track has started playing.
#
# Actions required: Actions required:
# - Note start time - Note start time
# - Mark next-track row as current - Mark next-track row as current
# - Mark current row as played - Mark current row as played
# - Scroll to put current track as required - Scroll to put current track as required
# - Set next track - Set next track
# - Update display - Update display
# """ """
#
# # Note start time # Note start time
# self.current_track_start_time = datetime.now() self.current_track_start_time = datetime.now()
#
# # Mark next-track row as current # Mark next-track row as current
# current_row = self._get_next_track_row() current_row = self._get_next_track_row()
# if current_row is None: if current_row is None:
# return return
# self._set_current_track_row(current_row) self._set_current_track_row(current_row)
#
# # Mark current row as played # Mark current row as played
# self._set_played_row(current_row) self._set_played_row(session, current_row)
#
# # Scroll to put current track as requiredin middle We want this # Scroll to put current track Config.SCROLL_TOP_MARGIN from the
# # row to be Config.SCROLL_TOP_MARGIN from the top. Rows number # top. Rows number from zero, so set (current_row -
# # from zero, so set (current_row - Config.SCROLL_TOP_MARGIN + 1) # Config.SCROLL_TOP_MARGIN + 1) row to be top row
# # row to be top row
# top_row = max(0, current_row - Config.SCROLL_TOP_MARGIN + 1)
# top_row = max(0, current_row - Config.SCROLL_TOP_MARGIN + 1) scroll_item = self.item(top_row, 0)
# scroll_item = self.item(top_row, self.COL_MSS) self.scrollToItem(scroll_item, QAbstractItemView.PositionAtTop)
# self.scrollToItem(scroll_item, QAbstractItemView.PositionAtTop)
# # Set next track
# # Set next track search_from = current_row + 1
# search_from = current_row + 1 next_row = self._find_next_track_row(session, search_from)
# next_row = self._find_next_track_row(search_from) if next_row:
# if next_row: self._set_next(session, next_row)
# self._set_next(session, next_row)
# # Update display
# # Update display self.update_display(session)
# self.update_display(session)
# def play_stopped(self) -> None:
# def play_stopped(self) -> None: """
# """ Notification from musicmuster that track has ended.
# Notification from musicmuster that track has ended.
# Actions required:
# Actions required: - Remove current track marker
# - Remove current track marker - Reset current track start time
# - Reset current track start time """
# - Update display
# """ self._clear_current_track_row()
# self.current_track_start_time = None
# self._clear_current_track_row()
# self.current_track_start_time = None
def populate(self, session: Session, playlist_id: int) -> None: def populate(self, session: Session, playlist_id: int) -> None:
""" """
@ -687,12 +683,14 @@ class PlaylistTab(QTableWidget):
# row: int # row: int
# track: Tracks # track: Tracks
playlist = session.get(Playlists, playlist_id) # Sanity check row numbering before we load
PlaylistRows.fixup_rownumbers(session, playlist_id)
# Clear playlist # Clear playlist
self.setRowCount(0) self.setRowCount(0)
# Add the rows # Add the rows
playlist = session.get(Playlists, playlist_id)
for row in playlist.rows: for row in playlist.rows:
self.insert_row(session, row, repaint=False) self.insert_row(session, row, repaint=False)
@ -802,7 +800,7 @@ class PlaylistTab(QTableWidget):
# if row in notes_rows: # if row in notes_rows:
# continue # continue
# track_id: int = self.item( # track_id: int = self.item(
# row, self.COL_USERDATA).data(self.CONTENT_OBJECT) # row, FIXUP.COL_USERDATA).data(self.CONTENT_OBJECT)
# playlist.add_track(session, track_id, row) # playlist.add_track(session, track_id, row)
# session.commit() # session.commit()
# #
@ -903,16 +901,16 @@ class PlaylistTab(QTableWidget):
# self.row_filter = text # self.row_filter = text
# with Session() as session: # with Session() as session:
# self.update_display(session) # self.update_display(session)
#
# def set_selected_as_next(self) -> None: def set_selected_as_next(self) -> None:
# """Sets the select track as next to play""" """Sets the select track as next to play"""
#
# row = self.get_selected_row() row = self.get_selected_row()
# if row is None: if row is None:
# return None return None
#
# with Session() as session: with Session() as session:
# self._set_next(session, row) self._set_next(session, row)
def update_display(self, session, clear_selection: bool = True) -> None: def update_display(self, session, clear_selection: bool = True) -> None:
""" """
@ -932,7 +930,7 @@ class PlaylistTab(QTableWidget):
current_row: Optional[int] = self._get_current_track_row() current_row: Optional[int] = self._get_current_track_row()
next_row: Optional[int] = self._get_next_track_row() next_row: Optional[int] = self._get_next_track_row()
played: Optional[List[int]] = self._get_played_track_rows() played = PlaylistRows.get_played_rows(session, self.playlist_id)
unreadable: List[int] = self._get_unreadable_track_rows() unreadable: List[int] = self._get_unreadable_track_rows()
if self.row_filter: if self.row_filter:
@ -969,7 +967,7 @@ class PlaylistTab(QTableWidget):
if track: if track:
# Render unplayable tracks in correct colour # Render unplayable tracks in correct colour
if not helpers.file_is_readable(track.path): if not file_is_readable(track.path):
self._set_row_colour(row, QColor(Config.COLOUR_UNREADABLE)) self._set_row_colour(row, QColor(Config.COLOUR_UNREADABLE))
self._set_row_bold(row) self._set_row_bold(row)
continue continue
@ -1005,7 +1003,7 @@ class PlaylistTab(QTableWidget):
self._set_row_start_time( self._set_row_start_time(
row, self.current_track_start_time) row, self.current_track_start_time)
# Set last played time to "Today" # Set last played time to "Today"
self.item(row, self.COL_LAST_PLAYED).setText("Today") self.item(row, columns['lastplayed'].idx).setText("Today")
# Calculate next_start_time # Calculate next_start_time
next_start_time = self._calculate_end_time( next_start_time = self._calculate_end_time(
self.current_track_start_time, track.duration) self.current_track_start_time, track.duration)
@ -1050,10 +1048,8 @@ class PlaylistTab(QTableWidget):
if row in played: if row in played:
# Played today, so update last played column # Played today, so update last played column
last_playedtime = track.lastplayed self.item(row, columns['lastplayed'].idx).setText(
last_played_str = get_relative_date(last_playedtime) Config.LAST_PLAYED_TODAY_STRING)
self.item(row, self.COL_LAST_PLAYED).setText(
last_played_str)
if self.musicmuster.hide_played_tracks: if self.musicmuster.hide_played_tracks:
self.hideRow(row) self.hideRow(row)
else: else:
@ -1168,7 +1164,7 @@ class PlaylistTab(QTableWidget):
# #
# if not self.editing_cell: # if not self.editing_cell:
# return # return
# if column not in [self.COL_TITLE, self.COL_ARTIST]: # if column not in [FIXUP.COL_TITLE, FIXUP.COL_ARTIST]:
# return # return
# #
# new_text: str = self.item(row, column).text() # new_text: str = self.item(row, column).text()
@ -1196,9 +1192,9 @@ class PlaylistTab(QTableWidget):
# ) # )
# else: # else:
# track: Tracks = self._get_row_track_object(row, session) # track: Tracks = self._get_row_track_object(row, session)
# if column == self.COL_ARTIST: # if column == FIXUP.COL_ARTIST:
# track.update_artist(session, artist=new_text) # track.update_artist(session, artist=new_text)
# elif column == self.COL_TITLE: # elif column == FIXUP.COL_TITLE:
# track.update_title(session, title=new_text) # track.update_title(session, title=new_text)
# else: # else:
# log.error("_cell_changed(): unrecognised column") # log.error("_cell_changed(): unrecognised column")
@ -1236,29 +1232,25 @@ class PlaylistTab(QTableWidget):
# # database. # # database.
# #
# if self._is_note_row(row): # if self._is_note_row(row):
# item = self.item(row, self.COL_TITLE) # item = self.item(row, FIXUP.COL_TITLE)
# with Session() as session: # with Session() as session:
# note_object = self._get_row_notes_object(row, session) # note_object = self._get_row_notes_object(row, session)
# if note_object: # if note_object:
# item.setText(note_object.note) # item.setText(note_object.note)
# return # return
#
# def _clear_current_track_row(self) -> None: def _clear_current_track_row(self) -> None:
# """ """
# Clear current row if there is one. Clear current row if there is one.
# """ """
#
# current_row: Optional[int] = self._get_current_track_row() current_row = self._get_current_track_row()
# if current_row is not None:
# self._meta_clear_attribute(current_row, RowMeta.CURRENT) if current_row is None:
# # Reset row colour return
# if current_row % 2:
# self._set_row_colour( self._meta_clear_attribute(current_row, RowMeta.CURRENT)
# current_row, QColor(Config.COLOUR_ODD_PLAYLIST))
# else:
# self._set_row_colour(
# current_row, QColor(Config.COLOUR_EVEN_PLAYLIST))
#
# def _clear_played_row_status(self, row: int) -> None: # def _clear_played_row_status(self, row: int) -> None:
# """Clear played status on row""" # """Clear played status on row"""
# #
@ -1299,7 +1291,7 @@ class PlaylistTab(QTableWidget):
# def _edit_note_cell(self, row, column): # review # def _edit_note_cell(self, row, column): # review
# """Called when table is single-clicked""" # """Called when table is single-clicked"""
# #
# if column in [self.COL_ROW_NOTES]: # if column in [FIXUP.COL_ROW_NOTES]:
# item = self.item(row, column) # item = self.item(row, column)
# self.editItem(item) # self.editItem(item)
# #
@ -1310,7 +1302,7 @@ class PlaylistTab(QTableWidget):
# column = mi.column() # column = mi.column()
# item = self.item(row, column) # item = self.item(row, column)
# #
# if column in [self.COL_TITLE, self.COL_ARTIST]: # if column in [FIXUP.COL_TITLE, FIXUP.COL_ARTIST]:
# self.editItem(item) # self.editItem(item)
# #
# def _get_notes_rows(self) -> List[int]: # def _get_notes_rows(self) -> List[int]:
@ -1335,32 +1327,30 @@ class PlaylistTab(QTableWidget):
return track_id return track_id
# def _find_next_track_row(self, starting_row: int = None) -> Optional[int]: def _find_next_track_row(self, session: Session,
# """ starting_row: int = None) -> Optional[int]:
# Find next track to play. If a starting row is given, start there; """
# else if there's a track selected, start looking from next track; Find next track to play. If a starting row is given, start there;
# otherwise, start from top. Skip rows already played. otherwise, start from top. Skip rows already played.
#
# If not found, return None. If not found, return None.
#
# If found, return row number. If found, return row number.
# """ """
#
# if starting_row is None: if starting_row is None:
# current_row = self._get_current_track_row() starting_row = 0
# if current_row is not None:
# starting_row = current_row + 1 track_rows = PlaylistRows.get_rows_with_tracks(session,
# else: self.playlist_id)
# starting_row = 0 played_rows = PlaylistRows.get_played_rows(session, self.playlist_id)
# notes_rows = self._get_notes_rows() for row in range(starting_row, self.rowCount()):
# played_rows = self._get_played_track_rows() if row not in track_rows or row in played_rows:
# for row in range(starting_row, self.rowCount()): continue
# if row in notes_rows or row in played_rows: else:
# continue return row
# else:
# return row return None
#
# return None
def _get_current_track_row(self) -> Optional[int]: def _get_current_track_row(self) -> Optional[int]:
"""Return row marked as current, or None""" """Return row marked as current, or None"""
@ -1394,11 +1384,6 @@ class PlaylistTab(QTableWidget):
except ValueError: except ValueError:
return None return None
def _get_played_track_rows(self) -> List[int]:
"""Return rows marked as played, or None"""
return self._meta_search(RowMeta.PLAYED, one=False)
def _get_row_duration(self, row: int) -> int: def _get_row_duration(self, row: int) -> int:
"""Return duration associated with this row""" """Return duration associated with this row"""
@ -1415,9 +1400,9 @@ class PlaylistTab(QTableWidget):
# """ # """
# #
# try: # try:
# if self.item(row, self.COL_END_TIME): # if self.item(row, FIXUP.COL_END_TIME):
# return datetime.strptime(self.item( # return datetime.strptime(self.item(
# row, self.COL_END_TIME).text(), # row, FIXUP.COL_END_TIME).text(),
# Config.NOTE_TIME_FORMAT # Config.NOTE_TIME_FORMAT
# ) # )
# else: # else:
@ -1429,7 +1414,7 @@ class PlaylistTab(QTableWidget):
# -> Optional[Notes]: # -> Optional[Notes]:
# """Return note associated with this row""" # """Return note associated with this row"""
# #
# note_id = self.item(row, self.COL_USERDATA).data(self.CONTENT_OBJECT) # note_id = self.item(row, FIXUP.COL_USERDATA).data(self.CONTENT_OBJECT)
# note = Notes.get_by_id(session, note_id) # note = Notes.get_by_id(session, note_id)
# return note # return note
# #
@ -1457,7 +1442,7 @@ class PlaylistTab(QTableWidget):
# -> Optional[Tracks]: # -> Optional[Tracks]:
# """Return track associated with this row""" # """Return track associated with this row"""
# #
# track_id = self.item(row, self.COL_USERDATA).data(self.CONTENT_OBJECT) # track_id = self.item(row, FIXUP.COL_USERDATA).data(self.CONTENT_OBJECT)
# track = Tracks.get_by_id(session, track_id) # track = Tracks.get_by_id(session, track_id)
# return track # return track
# #
@ -1481,9 +1466,9 @@ class PlaylistTab(QTableWidget):
f"Title: {track.title}\n" f"Title: {track.title}\n"
f"Artist: {track.artist}\n" f"Artist: {track.artist}\n"
f"Track ID: {track.id}\n" f"Track ID: {track.id}\n"
f"Track duration: {helpers.ms_to_mmss(track.duration)}\n" f"Track duration: {ms_to_mmss(track.duration)}\n"
f"Track fade at: {helpers.ms_to_mmss(track.fade_at)}\n" f"Track fade at: {ms_to_mmss(track.fade_at)}\n"
f"Track silence at: {helpers.ms_to_mmss(track.silence_at)}" f"Track silence at: {ms_to_mmss(track.silence_at)}"
"\n\n" "\n\n"
f"Path: {track.path}\n" f"Path: {track.path}\n"
) )
@ -1515,14 +1500,14 @@ class PlaylistTab(QTableWidget):
# # Add empty items to unused columns because # # Add empty items to unused columns because
# # colour won't be set for columns without items # # colour won't be set for columns without items
# item: QTableWidgetItem = QTableWidgetItem() # item: QTableWidgetItem = QTableWidgetItem()
# self.setItem(row, self.COL_USERDATA, item) # self.setItem(row, FIXUP.COL_USERDATA, item)
# item = QTableWidgetItem() # item = QTableWidgetItem()
# self.setItem(row, self.COL_MSS, item) # self.setItem(row, FIXUP.COL_MSS, item)
# #
# # Add text of note from title column onwards # # Add text of note from title column onwards
# titleitem: QTableWidgetItem = QTableWidgetItem(note.note) # titleitem: QTableWidgetItem = QTableWidgetItem(note.note)
# self.setItem(row, self.COL_NOTE, titleitem) # self.setItem(row, FIXUP.COL_NOTE, titleitem)
# self.setSpan(row, self.COL_NOTE, self.NOTE_ROW_SPAN, # self.setSpan(row, FIXUP.COL_NOTE, self.NOTE_ROW_SPAN,
# self.NOTE_COL_SPAN) # self.NOTE_COL_SPAN)
# #
# # Attach note id to row # # Attach note id to row
@ -1711,28 +1696,25 @@ class PlaylistTab(QTableWidget):
"""Run args in subprocess""" """Run args in subprocess"""
subprocess.call(args) subprocess.call(args)
#
# def _set_current_track_row(self, row: int) -> None: def _set_current_track_row(self, row: int) -> None:
# """Mark this row as current track""" """Mark this row as current track"""
#
# self._clear_current_track_row() self._clear_current_track_row()
# self._meta_set_attribute(row, RowMeta.CURRENT) self._meta_set_attribute(row, RowMeta.CURRENT)
def _set_next_track_row(self, row: int) -> None: def _set_next_track_row(self, row: int) -> None:
"""Mark this row as next track""" """Mark this row as next track"""
self._meta_clear_next() self._meta_clear_next()
self._meta_set_attribute(row, RowMeta.NEXT) self._meta_set_attribute(row, RowMeta.NEXT)
#
# def _set_note_row(self, row: int) -> None: def _set_played_row(self, session: Session, row: int) -> None:
# """Mark this row as a note""" """Mark this row as played"""
#
# self._meta_set_attribute(row, RowMeta.NOTE) plr = session.get(PlaylistRows, self._get_playlistrow_id(row))
# plr.played = True
# def _set_played_row(self, row: int) -> None: session.commit()
# """Mark this row as played"""
#
# self._meta_set_attribute(row, RowMeta.PLAYED)
def _set_unreadable_row(self, row: int) -> None: def _set_unreadable_row(self, row: int) -> None:
"""Mark this row as unreadable""" """Mark this row as unreadable"""
@ -1762,7 +1744,7 @@ class PlaylistTab(QTableWidget):
# Only paint message if there are selected track rows # Only paint message if there are selected track rows
if ms > 0: if ms > 0:
self.musicmuster.lblSumPlaytime.setText( self.musicmuster.lblSumPlaytime.setText(
f"Selected duration: {helpers.ms_to_mmss(ms)}") f"Selected duration: {ms_to_mmss(ms)}")
else: else:
self.musicmuster.lblSumPlaytime.setText("") self.musicmuster.lblSumPlaytime.setText("")
@ -1781,7 +1763,7 @@ class PlaylistTab(QTableWidget):
# """ # """
# #
# # Need to allow multiple rows to be selected # # Need to allow multiple rows to be selected
# self.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection) # self.setSelectionMode(QAbstractItemView.MultiSelection)
# self.clear_selection() # self.clear_selection()
# #
# if played: # if played:
@ -1793,7 +1775,7 @@ class PlaylistTab(QTableWidget):
# self.selectRow(row) # self.selectRow(row)
# #
# # Reset extended selection # # Reset extended selection
# self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) # self.setSelectionMode(QAbstractItemView.ExtendedSelection)
def _set_column_widths(self, session: Session) -> None: def _set_column_widths(self, session: Session) -> None:
"""Column widths from settings""" """Column widths from settings"""
@ -1832,7 +1814,7 @@ class PlaylistTab(QTableWidget):
return return
# Check track is readable # Check track is readable
if not helpers.file_is_readable(track.path): if not file_is_readable(track.path):
self._set_unreadable_row(row_number) self._set_unreadable_row(row_number)
return None return None
@ -1843,7 +1825,7 @@ class PlaylistTab(QTableWidget):
self.update_display(session) self.update_display(session)
# Notify musicmuster # Notify musicmuster
self.musicmuster.this_is_the_next_track(self, track, session) self.musicmuster.this_is_the_next_track(session, self, track)
def _set_row_bold(self, row: int, bold: bool = True) -> None: def _set_row_bold(self, row: int, bold: bool = True) -> None:
"""Make row bold (bold=True) or not bold""" """Make row bold (bold=True) or not bold"""
@ -1876,9 +1858,9 @@ class PlaylistTab(QTableWidget):
# def _set_row_content(self, row: int, object_id: int) -> None: # def _set_row_content(self, row: int, object_id: int) -> None:
# """Set content associated with this row""" # """Set content associated with this row"""
# #
# assert self.item(row, self.COL_USERDATA) # assert self.item(row, FIXUP.COL_USERDATA)
# #
# self.item(row, self.COL_USERDATA).setData( # self.item(row, FIXUP.COL_USERDATA).setData(
# self.CONTENT_OBJECT, object_id) # self.CONTENT_OBJECT, object_id)
def _set_row_duration(self, row: int, ms: int) -> None: def _set_row_duration(self, row: int, ms: int) -> None:
@ -1914,7 +1896,7 @@ class PlaylistTab(QTableWidget):
# def _set_timed_section(self, session, start_row, ms, no_end=False): # def _set_timed_section(self, session, start_row, ms, no_end=False):
# """Add duration to a marked section""" # """Add duration to a marked section"""
# #
# duration = helpers.ms_to_mmss(ms) # duration = ms_to_mmss(ms)
# note_object = self._get_row_notes_object(start_row, session) # note_object = self._get_row_notes_object(start_row, session)
# if not note_object: # if not note_object:
# log.error("Can't get note_object in playlists._set_timed_section") # log.error("Can't get note_object in playlists._set_timed_section")
@ -1923,7 +1905,7 @@ class PlaylistTab(QTableWidget):
# if no_end: # if no_end:
# caveat = " (to end of playlist)" # caveat = " (to end of playlist)"
# display_text = note_text + ' [' + duration + caveat + ']' # display_text = note_text + ' [' + duration + caveat + ']'
# item = self.item(start_row, self.COL_TITLE) # item = self.item(start_row, FIXUP.COL_TITLE)
# item.setText(display_text) # item.setText(display_text)
def _update_row(self, session, row: int, track: Tracks) -> None: def _update_row(self, session, row: int, track: Tracks) -> None:
@ -1946,6 +1928,6 @@ class PlaylistTab(QTableWidget):
item_artist.setText(track.artist) item_artist.setText(track.artist)
item_duration = self.item(row, columns['duration'].idx) item_duration = self.item(row, columns['duration'].idx)
item_duration.setText(helpers.ms_to_mmss(track.duration)) item_duration.setText(ms_to_mmss(track.duration))
self.update_display(session) self.update_display(session)

View File

@ -3,15 +3,15 @@ from PyQt5.QtGui import QFontMetrics, QPainter
from PyQt5.QtWidgets import QLabel from PyQt5.QtWidgets import QLabel
class ElideLabel(QLabel): # class ElideLabel(QLabel):
""" # """
From https://stackoverflow.com/questions/11446478/ # From https://stackoverflow.com/questions/11446478/
pyside-pyqt-truncate-text-in-qlabel-based-on-minimumsize # pyside-pyqt-truncate-text-in-qlabel-based-on-minimumsize
""" # """
#
def paintEvent(self, event): # def paintEvent(self, event):
painter = QPainter(self) # painter = QPainter(self)
metrics = QFontMetrics(self.font()) # metrics = QFontMetrics(self.font())
elided = metrics.elidedText(self.text(), Qt.ElideRight, self.width()) # elided = metrics.elidedText(self.text(), Qt.ElideRight, self.width())
#
painter.drawText(self.rect(), self.alignment(), elided) # painter.drawText(self.rect(), self.alignment(), elided)

View File

@ -264,7 +264,7 @@
# # Spike # # Spike
# # # #
# # # Manage tracks listed in database but where path is invalid # # # Manage tracks listed in database but where path is invalid
# # log.debug(f"Invalid {path=} in database", True) # # log.debug(f"Invalid {path=} in database")
# # track = Tracks.get_by_path(session, path) # # track = Tracks.get_by_path(session, path)
# # messages.append(f"Remove from database: {path=} {track=}") # # messages.append(f"Remove from database: {path=} {track=}")
# # # #
@ -279,10 +279,10 @@
# # for playlist_track in track.playlists: # # for playlist_track in track.playlists:
# # row = playlist_track.row # # row = playlist_track.row
# # # Remove playlist entry # # # Remove playlist entry
# # log.debug(f"Remove {row=} from {playlist_track.playlist_id}", True) # # log.debug(f"Remove {row=} from {playlist_track.playlist_id}")
# # playlist_track.playlist.remove_track(session, row) # # playlist_track.playlist.remove_track(session, row)
# # # Create note # # # Create note
# # log.debug(f"Add note at {row=} to {playlist_track.playlist_id=}", True) # # log.debug(f"Add note at {row=} to {playlist_track.playlist_id=}")
# # Notes(session, playlist_track.playlist_id, row, note_txt) # # Notes(session, playlist_track.playlist_id, row, note_txt)
# # # #
# # # Remove Track entry pointing to invalid path # # # Remove Track entry pointing to invalid path

View File

@ -0,0 +1,32 @@
"""Add 'played' column to playlist_rows
Revision ID: 0c604bf490f8
Revises: 29c0d7ffc741
Create Date: 2022-08-12 14:12:38.419845
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import mysql
# revision identifiers, used by Alembic.
revision = '0c604bf490f8'
down_revision = '29c0d7ffc741'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('playlist_rows', sa.Column('played', sa.Boolean(), nullable=False))
op.drop_index('ix_tracks_lastplayed', table_name='tracks')
op.drop_column('tracks', 'lastplayed')
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('tracks', sa.Column('lastplayed', mysql.DATETIME(), nullable=True))
op.create_index('ix_tracks_lastplayed', 'tracks', ['lastplayed'], unique=False)
op.drop_column('playlist_rows', 'played')
# ### end Alembic commands ###