Tabbed playlists working
This commit is contained in:
parent
ed2a766c80
commit
51b2dd43e5
15
app/model.py
15
app/model.py
@ -5,6 +5,7 @@ import sqlalchemy
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from sqlalchemy.ext.declarative import declarative_base
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
from sqlalchemy import (
|
from sqlalchemy import (
|
||||||
|
Boolean,
|
||||||
Column,
|
Column,
|
||||||
DateTime,
|
DateTime,
|
||||||
Float,
|
Float,
|
||||||
@ -153,6 +154,8 @@ class Playlists(Base):
|
|||||||
|
|
||||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||||
name = Column(String(32), nullable=False, unique=True)
|
name = Column(String(32), nullable=False, unique=True)
|
||||||
|
last_used = Column(DateTime, default=None, nullable=True)
|
||||||
|
loaded = Column(Boolean, default=True)
|
||||||
notes = relationship("Notes",
|
notes = relationship("Notes",
|
||||||
order_by="Notes.row",
|
order_by="Notes.row",
|
||||||
back_populates="playlist")
|
back_populates="playlist")
|
||||||
@ -172,6 +175,18 @@ class Playlists(Base):
|
|||||||
session.commit()
|
session.commit()
|
||||||
return pl.id
|
return pl.id
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_last_used():
|
||||||
|
"""
|
||||||
|
Return a list of playlists marked "loaded", ordered by loaded date.
|
||||||
|
"""
|
||||||
|
|
||||||
|
return (
|
||||||
|
session.query(Playlists)
|
||||||
|
.filter(Playlists.loaded == True)
|
||||||
|
.order_by(Playlists.last_used.desc())
|
||||||
|
).all()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_all_playlists():
|
def get_all_playlists():
|
||||||
"Returns a list of (id, name) of all playlists"
|
"Returns a list of (id, name) of all playlists"
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import threading
|
|||||||
import vlc
|
import vlc
|
||||||
|
|
||||||
from config import Config
|
from config import Config
|
||||||
|
from datetime import datetime
|
||||||
from time import sleep
|
from time import sleep
|
||||||
|
|
||||||
from log import DEBUG, ERROR
|
from log import DEBUG, ERROR
|
||||||
@ -14,6 +15,7 @@ class Music:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, max_volume=100):
|
def __init__(self, max_volume=100):
|
||||||
|
self.current_track_start_time = None
|
||||||
self.fading = False
|
self.fading = False
|
||||||
self.VLC = vlc.Instance()
|
self.VLC = vlc.Instance()
|
||||||
self.player = None
|
self.player = None
|
||||||
@ -99,6 +101,7 @@ class Music:
|
|||||||
self.player = self.VLC.media_player_new(path)
|
self.player = self.VLC.media_player_new(path)
|
||||||
self.player.audio_set_volume(self.max_volume)
|
self.player.audio_set_volume(self.max_volume)
|
||||||
self.player.play()
|
self.player.play()
|
||||||
|
self.current_track_start_time = datetime.now()
|
||||||
|
|
||||||
def playing(self):
|
def playing(self):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -1,27 +1,33 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from log import DEBUG, EXCEPTION
|
from log import DEBUG, EXCEPTION
|
||||||
|
|
||||||
|
from PyQt5 import Qt
|
||||||
from PyQt5.QtCore import QTimer
|
from PyQt5.QtCore import QTimer
|
||||||
from PyQt5.QtWidgets import (
|
from PyQt5.QtWidgets import (
|
||||||
QApplication,
|
QApplication,
|
||||||
|
QDialog,
|
||||||
QFileDialog,
|
QFileDialog,
|
||||||
QInputDialog,
|
QInputDialog,
|
||||||
QLabel,
|
QListWidgetItem,
|
||||||
QMainWindow,
|
QMainWindow,
|
||||||
QMessageBox,
|
QMessageBox,
|
||||||
)
|
)
|
||||||
|
|
||||||
import helpers
|
import helpers
|
||||||
|
import music
|
||||||
|
|
||||||
from config import Config
|
from config import Config
|
||||||
from model import Settings
|
from model import Playdates, Playlists, Settings, Tracks
|
||||||
from songdb import add_path_to_db
|
|
||||||
from playlists import Playlist
|
from playlists import Playlist
|
||||||
|
from songdb import add_path_to_db
|
||||||
|
from ui.dlg_search_database_ui import Ui_Dialog
|
||||||
|
from ui.dlg_SelectPlaylist_ui import Ui_dlgSelectPlaylist
|
||||||
from ui.main_window_ui import Ui_MainWindow
|
from ui.main_window_ui import Ui_MainWindow
|
||||||
|
|
||||||
|
|
||||||
@ -36,34 +42,20 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
self.connect_signals_slots()
|
self.connect_signals_slots()
|
||||||
self.disable_play_next_controls()
|
self.disable_play_next_controls()
|
||||||
|
|
||||||
|
self.music = music.Music()
|
||||||
|
self.current_track = None
|
||||||
|
self.next_track = None
|
||||||
|
self.previous_track = None
|
||||||
|
self.previous_track_position = None
|
||||||
|
|
||||||
self.menuTest.menuAction().setVisible(Config.TESTMODE)
|
self.menuTest.menuAction().setVisible(Config.TESTMODE)
|
||||||
|
self.set_main_window_size()
|
||||||
|
|
||||||
record = Settings.get_int("mainwindow_x")
|
self.current_playlist = self.tabPlaylist.currentWidget
|
||||||
x = record.f_int or 1
|
|
||||||
record = Settings.get_int("mainwindow_y")
|
|
||||||
y = record.f_int or 1
|
|
||||||
record = Settings.get_int("mainwindow_width")
|
|
||||||
width = record.f_int or 1599
|
|
||||||
record = Settings.get_int("mainwindow_height")
|
|
||||||
height = record.f_int or 981
|
|
||||||
self.setGeometry(x, y, width, height)
|
|
||||||
|
|
||||||
# self.playlist.set_column_widths()
|
self.load_last_playlists()
|
||||||
|
self.enable_play_next_controls()
|
||||||
# Hard code to the first playlist for now
|
self.timer.start(Config.TIMER_MS)
|
||||||
# TODO
|
|
||||||
# self.playlist = Playlist()
|
|
||||||
# self.playlist.load_playlist(1)
|
|
||||||
# self.tabPlaylist.addTab(self.playlist, "Default")
|
|
||||||
|
|
||||||
# self.playlist.load_playlist(1)
|
|
||||||
# self.update_headers()
|
|
||||||
# self.enable_play_next_controls()
|
|
||||||
|
|
||||||
# self.plLabel = QLabel(f"Playlist: {self.playlist.playlist_name}")
|
|
||||||
# self.statusbar.addPermanentWidget(self.plLabel)
|
|
||||||
|
|
||||||
# self.timer.start(Config.TIMER_MS)
|
|
||||||
|
|
||||||
def add_file(self):
|
def add_file(self):
|
||||||
dlg = QFileDialog()
|
dlg = QFileDialog()
|
||||||
@ -75,12 +67,28 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
if dlg.exec_():
|
if dlg.exec_():
|
||||||
for fname in dlg.selectedFiles():
|
for fname in dlg.selectedFiles():
|
||||||
track = add_path_to_db(fname)
|
track = add_path_to_db(fname)
|
||||||
self.playlist.add_to_playlist(track)
|
self.current_playlist().add_to_playlist(track)
|
||||||
|
|
||||||
|
def set_main_window_size(self):
|
||||||
|
|
||||||
|
record = Settings.get_int("mainwindow_x")
|
||||||
|
x = record.f_int or 1
|
||||||
|
record = Settings.get_int("mainwindow_y")
|
||||||
|
y = record.f_int or 1
|
||||||
|
record = Settings.get_int("mainwindow_width")
|
||||||
|
width = record.f_int or 1599
|
||||||
|
record = Settings.get_int("mainwindow_height")
|
||||||
|
height = record.f_int or 981
|
||||||
|
self.setGeometry(x, y, width, height)
|
||||||
|
|
||||||
|
def clear_selection(self):
|
||||||
|
if self.current_playlist():
|
||||||
|
self.current_playlist().clearSelection()
|
||||||
|
|
||||||
def closeEvent(self, event):
|
def closeEvent(self, event):
|
||||||
"Don't allow window to close when a track is playing"
|
"Don't allow window to close when a track is playing"
|
||||||
|
|
||||||
if self.playlist.music.playing():
|
if self.music.playing():
|
||||||
DEBUG("closeEvent() ignored as music is playing")
|
DEBUG("closeEvent() ignored as music is playing")
|
||||||
event.ignore()
|
event.ignore()
|
||||||
# TODO notify user
|
# TODO notify user
|
||||||
@ -107,8 +115,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
|
|
||||||
def connect_signals_slots(self):
|
def connect_signals_slots(self):
|
||||||
self.actionAdd_file.triggered.connect(self.add_file)
|
self.actionAdd_file.triggered.connect(self.add_file)
|
||||||
# self.action_Clear_selection.triggered.connect(
|
self.action_Clear_selection.triggered.connect(self.clear_selection)
|
||||||
# self.playlist.clearSelection)
|
|
||||||
self.actionFade.triggered.connect(self.fade)
|
self.actionFade.triggered.connect(self.fade)
|
||||||
self.actionNewPlaylist.triggered.connect(self.create_playlist)
|
self.actionNewPlaylist.triggered.connect(self.create_playlist)
|
||||||
self.actionPlay_next.triggered.connect(self.play_next)
|
self.actionPlay_next.triggered.connect(self.play_next)
|
||||||
@ -126,8 +133,9 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
self.btnPrevious.clicked.connect(self.play_previous)
|
self.btnPrevious.clicked.connect(self.play_previous)
|
||||||
self.btnSetNext.clicked.connect(self.set_next_track)
|
self.btnSetNext.clicked.connect(self.set_next_track)
|
||||||
self.btnSkipNext.clicked.connect(self.play_next)
|
self.btnSkipNext.clicked.connect(self.play_next)
|
||||||
# self.btnStop.clicked.connect(self.playlist.stop)
|
self.btnStop.clicked.connect(self.stop)
|
||||||
self.spnVolume.valueChanged.connect(self.change_volume)
|
self.spnVolume.valueChanged.connect(self.change_volume)
|
||||||
|
self.tabPlaylist.currentChanged.connect(self.tab_change)
|
||||||
|
|
||||||
self.timer.timeout.connect(self.tick)
|
self.timer.timeout.connect(self.tick)
|
||||||
|
|
||||||
@ -140,14 +148,14 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
dlg.resize(500, 100)
|
dlg.resize(500, 100)
|
||||||
ok = dlg.exec()
|
ok = dlg.exec()
|
||||||
if ok:
|
if ok:
|
||||||
self.playlist.create_playlist(dlg.textValue())
|
self.current_playlist().create_playlist(dlg.textValue())
|
||||||
|
|
||||||
def change_volume(self, volume):
|
def change_volume(self, volume):
|
||||||
"Change player maximum volume"
|
"Change player maximum volume"
|
||||||
|
|
||||||
DEBUG(f"change_volume({volume})")
|
DEBUG(f"change_volume({volume})")
|
||||||
|
|
||||||
self.playlist.music.set_volume(volume)
|
self.music.set_volume(volume)
|
||||||
|
|
||||||
def disable_play_next_controls(self):
|
def disable_play_next_controls(self):
|
||||||
DEBUG("disable_play_next_controls()")
|
DEBUG("disable_play_next_controls()")
|
||||||
@ -158,8 +166,13 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
self.actionPlay_next.setEnabled(True)
|
self.actionPlay_next.setEnabled(True)
|
||||||
|
|
||||||
def fade(self):
|
def fade(self):
|
||||||
self.playlist.fade()
|
"Fade currently playing track"
|
||||||
self.enable_play_next_controls()
|
|
||||||
|
if not self.current_track:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.previous_track = self.current_track
|
||||||
|
self.previous_track_position = self.music.fade()
|
||||||
|
|
||||||
def insert_note(self):
|
def insert_note(self):
|
||||||
"Add non-track row to playlist"
|
"Add non-track row to playlist"
|
||||||
@ -170,21 +183,89 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
dlg.resize(500, 100)
|
dlg.resize(500, 100)
|
||||||
ok = dlg.exec()
|
ok = dlg.exec()
|
||||||
if ok:
|
if ok:
|
||||||
self.playlist.add_note(dlg.textValue())
|
self.current_playlist().add_note(dlg.textValue())
|
||||||
|
|
||||||
|
def load_last_playlists(self):
|
||||||
|
"Load the playlists that we loaded at end of last session"
|
||||||
|
|
||||||
|
for p in Playlists.get_last_used():
|
||||||
|
playlist = Playlist()
|
||||||
|
playlist.load_playlist(p.id)
|
||||||
|
last_tab = self.tabPlaylist.addTab(playlist, p.name)
|
||||||
|
|
||||||
|
# Set last tab as active
|
||||||
|
self.tabPlaylist.setCurrentIndex(last_tab)
|
||||||
|
# Get next track
|
||||||
|
self.next_track = Tracks.get_track(
|
||||||
|
self.current_playlist().get_next_track_id())
|
||||||
|
self.update_headers()
|
||||||
|
|
||||||
def play_next(self):
|
def play_next(self):
|
||||||
self.playlist.play_next()
|
"""
|
||||||
|
Play next track.
|
||||||
|
|
||||||
|
If there is no next track set, return.
|
||||||
|
If there's currently a track playing, fade it.
|
||||||
|
Move next track to current track.
|
||||||
|
Play (new) current.
|
||||||
|
Update playlist "current track" metadata
|
||||||
|
Cue up next track in playlist if there is one.
|
||||||
|
Tell database to record it as played
|
||||||
|
Remember it was played for this session
|
||||||
|
Update metadata and headers, and repaint
|
||||||
|
"""
|
||||||
|
|
||||||
|
# If there is no next track set, return.
|
||||||
|
if not self.next_track:
|
||||||
|
return
|
||||||
|
|
||||||
|
DEBUG(
|
||||||
|
"play_next(), "
|
||||||
|
f"next_track={self.next_track.title if self.next_track else None} "
|
||||||
|
"current_track="
|
||||||
|
f"{self.current_track.title if self.current_track else None}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# If there's currently a track playing, fade it.
|
||||||
|
if self.music.playing():
|
||||||
|
self.previous_track_position = self.music.fade()
|
||||||
|
self.previous_track = self.current_track
|
||||||
|
|
||||||
|
# Shuffle tracks along
|
||||||
|
self.current_track = self.next_track
|
||||||
|
self.next_track = None
|
||||||
|
|
||||||
|
# Play (new) current.
|
||||||
|
self.music.play(self.current_track.path)
|
||||||
|
|
||||||
|
# Update metadata
|
||||||
|
next_track_id = self.current_playlist().started_playing_next()
|
||||||
|
|
||||||
|
self.next_track = Tracks.get_track(next_track_id)
|
||||||
|
# Check we can read it
|
||||||
|
if not os.access(self.next_track.path, os.R_OK):
|
||||||
|
self.show_warning(
|
||||||
|
"Can't read next track",
|
||||||
|
self.next_track.path)
|
||||||
|
|
||||||
|
# Tell database to record it as played
|
||||||
|
self.current_track.update_lastplayed()
|
||||||
|
Playdates.add_playdate(self.current_track)
|
||||||
|
|
||||||
|
# Remember it was played for this session
|
||||||
|
self.current_playlist().mark_track_played(self.current_track.id)
|
||||||
|
|
||||||
self.disable_play_next_controls()
|
self.disable_play_next_controls()
|
||||||
self.update_headers()
|
self.update_headers()
|
||||||
|
|
||||||
# Set time clocks
|
# Set time clocks
|
||||||
now = datetime.now()
|
now = datetime.now()
|
||||||
self.label_start_tod.setText(now.strftime("%H:%M:%S"))
|
self.label_start_tod.setText(now.strftime("%H:%M:%S"))
|
||||||
silence_at = self.playlist.get_current_silence_at()
|
silence_at = self.current_track.silence_at
|
||||||
silence_time = now + timedelta(milliseconds=silence_at)
|
silence_time = now + timedelta(milliseconds=silence_at)
|
||||||
self.label_silent_tod.setText(silence_time.strftime("%H:%M:%S"))
|
self.label_silent_tod.setText(silence_time.strftime("%H:%M:%S"))
|
||||||
self.label_fade_length.setText(helpers.ms_to_mmss(
|
self.label_fade_length.setText(helpers.ms_to_mmss(
|
||||||
silence_at - self.playlist.get_current_fade_at()
|
silence_at - self.current_track.fade_at
|
||||||
))
|
))
|
||||||
|
|
||||||
def play_previous(self):
|
def play_previous(self):
|
||||||
@ -193,16 +274,27 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
# TODO
|
# TODO
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def playlist_changed(self):
|
||||||
|
"The playlist has changed (probably because the user changed tabs)"
|
||||||
|
|
||||||
|
self.next_track = Tracks.get_track(
|
||||||
|
self.current_playlist().get_next_track_id())
|
||||||
|
self.update_headers()
|
||||||
|
|
||||||
def search_database(self):
|
def search_database(self):
|
||||||
self.playlist.search_database()
|
dlg = DbDialog(self)
|
||||||
|
dlg.exec()
|
||||||
|
|
||||||
def select_playlist(self):
|
def select_playlist(self):
|
||||||
self.playlist.select_playlist()
|
dlg = SelectPlaylistDialog(self)
|
||||||
|
dlg.exec()
|
||||||
|
|
||||||
def set_next_track(self):
|
def set_next_track(self):
|
||||||
"Set selected track as next"
|
"Set selected track as next"
|
||||||
|
|
||||||
self.playlist.set_selected_as_next()
|
next_track_id = self.current_playlist().set_selected_as_next()
|
||||||
|
if next_track_id:
|
||||||
|
self.next_track = Tracks.get_track(next_track_id)
|
||||||
self.update_headers()
|
self.update_headers()
|
||||||
|
|
||||||
def show_warning(self, title, msg):
|
def show_warning(self, title, msg):
|
||||||
@ -210,37 +302,40 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
|
|
||||||
QMessageBox.warning(None, title, msg, buttons=QMessageBox.Cancel)
|
QMessageBox.warning(None, title, msg, buttons=QMessageBox.Cancel)
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
"Stop playing immediately"
|
||||||
|
|
||||||
|
self.previous_track = self.current_track
|
||||||
|
self.previous_track_position = self.music.stop()
|
||||||
|
|
||||||
|
def tab_change(self):
|
||||||
|
"User has changed tabs, so refresh next track"
|
||||||
|
|
||||||
|
self.next_track = Tracks.get_track(
|
||||||
|
self.current_playlist().get_next_track_id())
|
||||||
|
self.update_headers()
|
||||||
|
|
||||||
def test_function(self):
|
def test_function(self):
|
||||||
"Placeholder for test function"
|
"Placeholder for test function"
|
||||||
|
|
||||||
import ipdb
|
import ipdb
|
||||||
ipdb.set_trace()
|
ipdb.set_trace()
|
||||||
self.playlist = Playlist(parent=self.tabPlaylist)
|
|
||||||
self.tabPlaylist.addTab(self.playlist, "Default")
|
|
||||||
self.playlist.load_playlist(1)
|
|
||||||
self.playlist2 = Playlist(parent=self.tabPlaylist)
|
|
||||||
self.tabPlaylist.addTab(self.playlist2, "List 2")
|
|
||||||
self.playlist2.load_playlist(2)
|
|
||||||
|
|
||||||
def test_skip_to_end(self):
|
def test_skip_to_end(self):
|
||||||
"Skip current track to 1 second before silence"
|
"Skip current track to 1 second before silence"
|
||||||
|
|
||||||
if not self.playlist.music.playing():
|
if not self.playing():
|
||||||
return
|
return
|
||||||
|
|
||||||
self.playlist.music.set_position(
|
self.music.set_position(self.get_current_silence_at() - 1000)
|
||||||
self.playlist.get_current_silence_at() - 1000
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_skip_to_fade(self):
|
def test_skip_to_fade(self):
|
||||||
"Skip current track to 1 second before fade"
|
"Skip current track to 1 second before fade"
|
||||||
|
|
||||||
if not self.playlist.music.playing():
|
if not self.music.playing():
|
||||||
return
|
return
|
||||||
|
|
||||||
self.playlist.music.set_position(
|
self.music.set_position(self.get_current_fade_at() - 1000)
|
||||||
self.playlist.get_current_fade_at() - 1000
|
|
||||||
)
|
|
||||||
|
|
||||||
def tick(self):
|
def tick(self):
|
||||||
"""
|
"""
|
||||||
@ -262,19 +357,17 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
if not self.even_tick:
|
if not self.even_tick:
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.playlist.music.playing():
|
if self.music.playing():
|
||||||
self.playing = True
|
self.playing = True
|
||||||
playtime = self.playlist.music.get_playtime()
|
playtime = self.music.get_playtime()
|
||||||
time_to_fade = (self.playlist.get_current_fade_at() - playtime)
|
time_to_fade = (self.current_track.fade_at - playtime)
|
||||||
time_to_silence = (
|
time_to_silence = (self.current_track.silence_at - playtime)
|
||||||
self.playlist.get_current_silence_at() - playtime
|
time_to_end = (self.current_track.duration - playtime)
|
||||||
)
|
|
||||||
time_to_end = (self.playlist.get_current_duration() - playtime)
|
|
||||||
|
|
||||||
# Elapsed time
|
# Elapsed time
|
||||||
if time_to_end < 500:
|
if time_to_end < 500:
|
||||||
self.label_elapsed_timer.setText(
|
self.label_elapsed_timer.setText(
|
||||||
helpers.ms_to_mmss(self.playlist.get_current_duration())
|
helpers.ms_to_mmss(self.current_track.duration)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.label_elapsed_timer.setText(helpers.ms_to_mmss(playtime))
|
self.label_elapsed_timer.setText(helpers.ms_to_mmss(playtime))
|
||||||
@ -308,27 +401,136 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
self.label_end_timer.setText("00:00")
|
self.label_end_timer.setText("00:00")
|
||||||
self.frame_silent.setStyleSheet("")
|
self.frame_silent.setStyleSheet("")
|
||||||
self.playing = False
|
self.playing = False
|
||||||
self.playlist.music_ended()
|
self.previous_track = self.current_track
|
||||||
|
self.previous_track_position = 0
|
||||||
|
self.current_playlist().stopped_playing()
|
||||||
self.update_headers()
|
self.update_headers()
|
||||||
|
|
||||||
def update_headers(self):
|
def update_headers(self):
|
||||||
"Update last / current / next track headers"
|
"Update last / current / next track headers"
|
||||||
|
|
||||||
self.previous_track.setText(
|
try:
|
||||||
f"{self.playlist.get_previous_title()} - "
|
self.hdrPreviousTrack.setText(
|
||||||
f"{self.playlist.get_previous_artist()}"
|
f"{self.previous_track.title} - "
|
||||||
)
|
f"{self.previous_track.artist}"
|
||||||
self.current_track.setText(
|
)
|
||||||
f"{self.playlist.get_current_title()} - "
|
except AttributeError:
|
||||||
f"{self.playlist.get_current_artist()}"
|
self.hdrPreviousTrack.setText("")
|
||||||
)
|
|
||||||
self.next_track.setText(
|
|
||||||
f"{self.playlist.get_next_title()} - "
|
|
||||||
f"{self.playlist.get_next_artist()}"
|
|
||||||
)
|
|
||||||
|
|
||||||
def update_statusbar(self):
|
try:
|
||||||
pass
|
self.hdrCurrentTrack.setText(
|
||||||
|
f"{self.current_track.title} - "
|
||||||
|
f"{self.current_track.artist}"
|
||||||
|
)
|
||||||
|
except AttributeError:
|
||||||
|
self.hdrCurrentTrack.setText("")
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.hdrNextTrack.setText(
|
||||||
|
f"{self.next_track.title} - "
|
||||||
|
f"{self.next_track.artist}"
|
||||||
|
)
|
||||||
|
except AttributeError:
|
||||||
|
self.hdrNextTrack.setText("")
|
||||||
|
|
||||||
|
|
||||||
|
class DbDialog(QDialog):
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.ui = Ui_Dialog()
|
||||||
|
self.ui.setupUi(self)
|
||||||
|
self.ui.searchString.textEdited.connect(self.chars_typed)
|
||||||
|
self.ui.matchList.itemDoubleClicked.connect(self.double_click)
|
||||||
|
self.ui.btnAdd.clicked.connect(self.add_selected)
|
||||||
|
self.ui.btnAddClose.clicked.connect(self.add_selected_and_close)
|
||||||
|
self.ui.btnClose.clicked.connect(self.close)
|
||||||
|
|
||||||
|
record = Settings.get_int("dbdialog_width")
|
||||||
|
width = record.f_int or 800
|
||||||
|
record = Settings.get_int("dbdialog_height")
|
||||||
|
height = record.f_int or 600
|
||||||
|
self.resize(width, height)
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
record = Settings.get_int("dbdialog_height")
|
||||||
|
if record.f_int != self.height():
|
||||||
|
record.update({'f_int': self.height()})
|
||||||
|
|
||||||
|
record = Settings.get_int("dbdialog_width")
|
||||||
|
if record.f_int != self.width():
|
||||||
|
record.update({'f_int': self.width()})
|
||||||
|
|
||||||
|
def add_selected(self):
|
||||||
|
if not self.ui.matchList.selectedItems():
|
||||||
|
return
|
||||||
|
|
||||||
|
item = self.ui.matchList.currentItem()
|
||||||
|
track_id = item.data(Qt.UserRole)
|
||||||
|
self.add_track(track_id)
|
||||||
|
|
||||||
|
def add_selected_and_close(self):
|
||||||
|
self.add_selected()
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
def chars_typed(self, s):
|
||||||
|
if len(s) >= 3:
|
||||||
|
matches = Tracks.search_titles(s)
|
||||||
|
self.ui.matchList.clear()
|
||||||
|
if matches:
|
||||||
|
for track in matches:
|
||||||
|
t = QListWidgetItem()
|
||||||
|
t.setText(
|
||||||
|
f"{track.title} - {track.artist} "
|
||||||
|
f"[{helpers.ms_to_mmss(track.duration)}]"
|
||||||
|
)
|
||||||
|
t.setData(Qt.UserRole, track.id)
|
||||||
|
self.ui.matchList.addItem(t)
|
||||||
|
|
||||||
|
def double_click(self, entry):
|
||||||
|
track_id = entry.data(Qt.UserRole)
|
||||||
|
self.add_track(track_id)
|
||||||
|
# Select search text to make it easier for next search
|
||||||
|
self.select_searchtext()
|
||||||
|
|
||||||
|
def add_track(self, track_id):
|
||||||
|
track = Tracks.track_from_id(track_id)
|
||||||
|
self.parent().add_to_playlist(track)
|
||||||
|
# Select search text to make it easier for next search
|
||||||
|
self.select_searchtext()
|
||||||
|
|
||||||
|
def select_searchtext(self):
|
||||||
|
self.ui.searchString.selectAll()
|
||||||
|
self.ui.searchString.setFocus()
|
||||||
|
|
||||||
|
|
||||||
|
class SelectPlaylistDialog(QDialog):
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.ui = Ui_dlgSelectPlaylist()
|
||||||
|
self.ui.setupUi(self)
|
||||||
|
self.ui.lstPlaylists.itemDoubleClicked.connect(self.list_doubleclick)
|
||||||
|
self.ui.buttonBox.accepted.connect(self.open)
|
||||||
|
self.ui.buttonBox.rejected.connect(self.close)
|
||||||
|
|
||||||
|
for (plid, plname) in [
|
||||||
|
(a.id, a.name) for a in Playlists.get_all_playlists()
|
||||||
|
]:
|
||||||
|
p = QListWidgetItem()
|
||||||
|
p.setText(plname)
|
||||||
|
p.setData(Qt.UserRole, plid)
|
||||||
|
self.ui.lstPlaylists.addItem(p)
|
||||||
|
|
||||||
|
def list_doubleclick(self, entry):
|
||||||
|
plid = entry.data(Qt.UserRole)
|
||||||
|
self.parent().load_playlist(plid)
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
def open(self):
|
||||||
|
if self.ui.lstPlaylists.selectedItems():
|
||||||
|
item = self.ui.lstPlaylists.currentItem()
|
||||||
|
plid = item.data(Qt.UserRole)
|
||||||
|
self.parent().load_playlist(plid)
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|||||||
443
app/playlists.py
443
app/playlists.py
@ -5,27 +5,19 @@ from PyQt5.QtGui import QColor, QDropEvent
|
|||||||
from PyQt5 import QtWidgets
|
from PyQt5 import QtWidgets
|
||||||
from PyQt5.QtWidgets import (
|
from PyQt5.QtWidgets import (
|
||||||
QAbstractItemView,
|
QAbstractItemView,
|
||||||
QApplication,
|
|
||||||
QDialog,
|
|
||||||
QHBoxLayout,
|
|
||||||
QListWidgetItem,
|
|
||||||
QMenu,
|
QMenu,
|
||||||
QMessageBox,
|
QMessageBox,
|
||||||
QTableWidget,
|
QTableWidget,
|
||||||
QTableWidgetItem,
|
QTableWidgetItem,
|
||||||
QWidget,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
import helpers
|
import helpers
|
||||||
import music
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from config import Config
|
from config import Config
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from log import DEBUG, ERROR
|
from log import DEBUG, ERROR
|
||||||
from model import Notes, Playdates, Playlists, PlaylistTracks, Settings, Tracks
|
from model import Notes, Playlists, PlaylistTracks, Settings, Tracks
|
||||||
from ui.dlg_search_database_ui import Ui_Dialog
|
|
||||||
from ui.dlg_SelectPlaylist_ui import Ui_dlgSelectPlaylist
|
|
||||||
|
|
||||||
|
|
||||||
class Playlist(QTableWidget):
|
class Playlist(QTableWidget):
|
||||||
@ -67,6 +59,9 @@ class Playlist(QTableWidget):
|
|||||||
item = QtWidgets.QTableWidgetItem()
|
item = QtWidgets.QTableWidgetItem()
|
||||||
self.setHorizontalHeaderItem(6, item)
|
self.setHorizontalHeaderItem(6, item)
|
||||||
self.horizontalHeader().setMinimumSectionSize(0)
|
self.horizontalHeader().setMinimumSectionSize(0)
|
||||||
|
|
||||||
|
self._set_column_widths()
|
||||||
|
|
||||||
self.setDragEnabled(True)
|
self.setDragEnabled(True)
|
||||||
self.setAcceptDrops(True)
|
self.setAcceptDrops(True)
|
||||||
self.viewport().setAcceptDrops(True)
|
self.viewport().setAcceptDrops(True)
|
||||||
@ -85,13 +80,7 @@ class Playlist(QTableWidget):
|
|||||||
self.customContextMenuRequested.connect(self._context_menu)
|
self.customContextMenuRequested.connect(self._context_menu)
|
||||||
self.viewport().installEventFilter(self)
|
self.viewport().installEventFilter(self)
|
||||||
|
|
||||||
self.music = music.Music()
|
self.current_track_start_time = None
|
||||||
self.current_track = None
|
|
||||||
self.next_track = None
|
|
||||||
self.playlist_name = None
|
|
||||||
self.playlist_id = 0
|
|
||||||
self.previous_track = None
|
|
||||||
self.previous_track_position = None
|
|
||||||
self.played_tracks = []
|
self.played_tracks = []
|
||||||
|
|
||||||
# ########## Events ##########
|
# ########## Events ##########
|
||||||
@ -187,7 +176,7 @@ class Playlist(QTableWidget):
|
|||||||
row = self.rowCount()
|
row = self.rowCount()
|
||||||
DEBUG(f"playlist.add_note(): row={row}")
|
DEBUG(f"playlist.add_note(): row={row}")
|
||||||
|
|
||||||
note = Notes.add_note(self.playlist_id, row, text)
|
note = Notes.add_note(self.id, row, text)
|
||||||
self.add_to_playlist(note, row=row)
|
self.add_to_playlist(note, row=row)
|
||||||
|
|
||||||
def add_to_playlist(self, data, repaint=True, row=None):
|
def add_to_playlist(self, data, repaint=True, row=None):
|
||||||
@ -269,68 +258,10 @@ class Playlist(QTableWidget):
|
|||||||
new_id = Playlists.new(name)
|
new_id = Playlists.new(name)
|
||||||
self.load_playlist(new_id)
|
self.load_playlist(new_id)
|
||||||
|
|
||||||
def fade(self):
|
def get_next_track_id(self):
|
||||||
"Fade currently playing track"
|
|
||||||
|
|
||||||
if not self.current_track:
|
next_row = self._meta_get_next()
|
||||||
return
|
return self._get_row_id(next_row)
|
||||||
|
|
||||||
self.previous_track = self.current_track
|
|
||||||
self.previous_track_position = self.music.fade()
|
|
||||||
|
|
||||||
def get_current_artist(self):
|
|
||||||
try:
|
|
||||||
return self.current_track.artist
|
|
||||||
except AttributeError:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
def get_current_duration(self):
|
|
||||||
try:
|
|
||||||
return self.current_track.duration
|
|
||||||
except AttributeError:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def get_current_fade_at(self):
|
|
||||||
try:
|
|
||||||
return self.current_track.fade_at
|
|
||||||
except AttributeError:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def get_current_silence_at(self):
|
|
||||||
try:
|
|
||||||
return self.current_track.silence_at
|
|
||||||
except AttributeError:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def get_current_title(self):
|
|
||||||
try:
|
|
||||||
return self.current_track.title
|
|
||||||
except AttributeError:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
def get_next_artist(self):
|
|
||||||
try:
|
|
||||||
return self.next_track.artist
|
|
||||||
except AttributeError:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
def get_next_title(self):
|
|
||||||
try:
|
|
||||||
return self.next_track.title
|
|
||||||
except AttributeError:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
def get_previous_artist(self):
|
|
||||||
try:
|
|
||||||
return self.previous_track.artist
|
|
||||||
except AttributeError:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
def get_previous_title(self):
|
|
||||||
try:
|
|
||||||
return self.previous_track.title
|
|
||||||
except AttributeError:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
def load_playlist(self, plid):
|
def load_playlist(self, plid):
|
||||||
"""
|
"""
|
||||||
@ -344,9 +275,6 @@ class Playlist(QTableWidget):
|
|||||||
DEBUG(f"load_playlist(plid={plid})")
|
DEBUG(f"load_playlist(plid={plid})")
|
||||||
|
|
||||||
p = Playlists.get_playlist_by_id(plid)
|
p = Playlists.get_playlist_by_id(plid)
|
||||||
self.playlist_id = plid
|
|
||||||
self.playlist_name = p.name
|
|
||||||
# TODO self.parent().parent().update_statusbar()
|
|
||||||
|
|
||||||
# We need to retrieve playlist tracks and playlist notes, then
|
# We need to retrieve playlist tracks and playlist notes, then
|
||||||
# add them in row order. We don't mandate that an item will be
|
# add them in row order. We don't mandate that an item will be
|
||||||
@ -366,115 +294,22 @@ class Playlist(QTableWidget):
|
|||||||
for item in sorted(data, key=lambda x: x[0]):
|
for item in sorted(data, key=lambda x: x[0]):
|
||||||
self.add_to_playlist(item[1], repaint=False)
|
self.add_to_playlist(item[1], repaint=False)
|
||||||
|
|
||||||
# Set next track if we don't have one already set
|
# Set next track for this playlist
|
||||||
if not self.next_track:
|
notes_rows = self._meta_get_notes()
|
||||||
notes_rows = self._meta_get_notes()
|
for row in range(self.rowCount()):
|
||||||
for row in range(self.rowCount()):
|
if row in notes_rows:
|
||||||
if row in notes_rows:
|
continue
|
||||||
continue
|
self._meta_set_next(row)
|
||||||
self._cue_next_track(row)
|
break
|
||||||
break
|
|
||||||
else:
|
|
||||||
self._repaint()
|
|
||||||
|
|
||||||
# Scroll to top
|
# Scroll to top
|
||||||
scroll_to = self.item(0, self.COL_INDEX)
|
scroll_to = self.item(0, self.COL_INDEX)
|
||||||
self.scrollToItem(scroll_to, QAbstractItemView.PositionAtTop)
|
self.scrollToItem(scroll_to, QAbstractItemView.PositionAtTop)
|
||||||
|
|
||||||
def music_ended(self):
|
|
||||||
"Update display"
|
|
||||||
|
|
||||||
self.previous_track = self.current_track
|
|
||||||
self.previous_track_position = 0
|
|
||||||
self._meta_clear_current()
|
|
||||||
self._repaint()
|
self._repaint()
|
||||||
|
|
||||||
def play_next(self):
|
def mark_track_played(self, track_id):
|
||||||
"""
|
self.played_tracks.append(track_id)
|
||||||
Play next track.
|
|
||||||
|
|
||||||
If there is no next track set, return.
|
|
||||||
If there's currently a track playing, fade it.
|
|
||||||
Move next track to current track.
|
|
||||||
Play (new) current.
|
|
||||||
Update playlist "current track" metadata
|
|
||||||
Cue up next track in playlist if there is one.
|
|
||||||
Tell database to record it as played
|
|
||||||
Remember it was played for this session
|
|
||||||
Update metadata and headers, and repaint
|
|
||||||
"""
|
|
||||||
|
|
||||||
# If there is no next track set, return.
|
|
||||||
if not self.next_track:
|
|
||||||
return
|
|
||||||
|
|
||||||
DEBUG(
|
|
||||||
"playlist.play_next(), "
|
|
||||||
f"next_track={self.next_track.title if self.next_track else None} "
|
|
||||||
"current_track="
|
|
||||||
f"{self.current_track.title if self.current_track else None}"
|
|
||||||
)
|
|
||||||
|
|
||||||
# If there's currently a track playing, fade it.
|
|
||||||
if self.music.playing():
|
|
||||||
self.previous_track_position = self.music.fade()
|
|
||||||
self.previous_track = self.current_track
|
|
||||||
|
|
||||||
# Shuffle tracks along
|
|
||||||
self.current_track = self.next_track
|
|
||||||
self.next_track = None
|
|
||||||
|
|
||||||
# Play (new) current.
|
|
||||||
self.music.play(self.current_track.path)
|
|
||||||
self.current_track.start_time = datetime.now()
|
|
||||||
|
|
||||||
# Update metadata
|
|
||||||
self._meta_set_current(self._meta_get_next())
|
|
||||||
|
|
||||||
# Set up metadata for next track in playlist if there is one.
|
|
||||||
current_row = self._meta_get_current()
|
|
||||||
if current_row is not None:
|
|
||||||
start = current_row + 1
|
|
||||||
else:
|
|
||||||
start = 0
|
|
||||||
notes_rows = self._meta_get_notes()
|
|
||||||
for row in range(start, self.rowCount()):
|
|
||||||
if row in notes_rows:
|
|
||||||
continue
|
|
||||||
self._cue_next_track(row)
|
|
||||||
break
|
|
||||||
|
|
||||||
# Tell database to record it as played
|
|
||||||
self.current_track.update_lastplayed()
|
|
||||||
Playdates.add_playdate(self.current_track)
|
|
||||||
|
|
||||||
# Remember it was played for this session
|
|
||||||
self.played_tracks.append(self.current_track.id)
|
|
||||||
|
|
||||||
# Update display
|
|
||||||
self._repaint()
|
|
||||||
|
|
||||||
def search_database(self):
|
|
||||||
dlg = DbDialog(self)
|
|
||||||
dlg.exec()
|
|
||||||
|
|
||||||
def select_playlist(self):
|
|
||||||
dlg = SelectPlaylistDialog(self)
|
|
||||||
dlg.exec()
|
|
||||||
|
|
||||||
def set_column_widths(self):
|
|
||||||
|
|
||||||
# Column widths from settings
|
|
||||||
for column in range(self.columnCount()):
|
|
||||||
# Only show column 0 in test mode
|
|
||||||
if (column == 0 and not Config.TESTMODE):
|
|
||||||
self.setColumnWidth(0, 0)
|
|
||||||
else:
|
|
||||||
name = f"playlist_col_{str(column)}_width"
|
|
||||||
record = Settings.get_int(name)
|
|
||||||
if record.f_int is not None:
|
|
||||||
print("setting column width")
|
|
||||||
self.setColumnWidth(column, record.f_int)
|
|
||||||
|
|
||||||
def set_selected_as_next(self):
|
def set_selected_as_next(self):
|
||||||
"""
|
"""
|
||||||
@ -484,13 +319,21 @@ class Playlist(QTableWidget):
|
|||||||
if not self.selectionModel().hasSelection():
|
if not self.selectionModel().hasSelection():
|
||||||
return
|
return
|
||||||
|
|
||||||
self._set_next(self.currentRow())
|
return self._set_next(self.currentRow())
|
||||||
|
|
||||||
def stop(self):
|
def started_playing_next(self):
|
||||||
"Stop playing immediately"
|
"""
|
||||||
|
Update current track to be what was next, and determine next track.
|
||||||
|
Return next track_id.
|
||||||
|
"""
|
||||||
|
|
||||||
self.previous_track = self.current_track
|
self.current_track_start_time = datetime.now()
|
||||||
self.previous_track_position = self.music.stop()
|
self._meta_set_current(self._meta_get_next())
|
||||||
|
return self._mark_next_track()
|
||||||
|
|
||||||
|
def stopped_playing(self):
|
||||||
|
self._meta_clear_current()
|
||||||
|
self.current_track_start_time = None
|
||||||
|
|
||||||
# ########## Internally called functions ##########
|
# ########## Internally called functions ##########
|
||||||
|
|
||||||
@ -515,27 +358,6 @@ class Playlist(QTableWidget):
|
|||||||
|
|
||||||
self.menu.exec_(self.mapToGlobal(pos))
|
self.menu.exec_(self.mapToGlobal(pos))
|
||||||
|
|
||||||
def _cue_next_track(self, row):
|
|
||||||
"""
|
|
||||||
Set the passed row as the next track to play
|
|
||||||
"""
|
|
||||||
|
|
||||||
track_id = self._get_row_id(row)
|
|
||||||
if not track_id:
|
|
||||||
return
|
|
||||||
|
|
||||||
self._meta_set_next(row)
|
|
||||||
|
|
||||||
if not self.next_track or self.next_track.id != track_id:
|
|
||||||
self.next_track = Tracks.get_track(track_id)
|
|
||||||
# Check we can read it
|
|
||||||
if not self._can_read_track(self.next_track):
|
|
||||||
self.parent().parent().show_warning(
|
|
||||||
"Can't read next track",
|
|
||||||
self.next_track.path)
|
|
||||||
|
|
||||||
self._repaint()
|
|
||||||
|
|
||||||
def _delete_row(self, row):
|
def _delete_row(self, row):
|
||||||
"Delete row"
|
"Delete row"
|
||||||
|
|
||||||
@ -564,7 +386,7 @@ class Playlist(QTableWidget):
|
|||||||
if row in self._meta_get_notes():
|
if row in self._meta_get_notes():
|
||||||
Notes.delete_note(id)
|
Notes.delete_note(id)
|
||||||
else:
|
else:
|
||||||
PlaylistTracks.remove_track(self.playlist_id, row)
|
PlaylistTracks.remove_track(self.id, row)
|
||||||
self.removeRow(row)
|
self.removeRow(row)
|
||||||
|
|
||||||
self._repaint()
|
self._repaint()
|
||||||
@ -610,13 +432,32 @@ class Playlist(QTableWidget):
|
|||||||
return False
|
return False
|
||||||
elif rect.bottom() - pos.y() < margin:
|
elif rect.bottom() - pos.y() < margin:
|
||||||
return True
|
return True
|
||||||
# noinspection PyTypeChecker
|
|
||||||
return (
|
return (
|
||||||
rect.contains(pos, True) and not
|
rect.contains(pos, True) and not
|
||||||
(int(self.model().flags(index)) & Qt.ItemIsDropEnabled)
|
(int(self.model().flags(index)) & Qt.ItemIsDropEnabled)
|
||||||
and pos.y() >= rect.center().y() # noqa W503
|
and pos.y() >= rect.center().y() # noqa W503
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _mark_next_track(self):
|
||||||
|
"Set up metadata for next track in playlist if there is one."
|
||||||
|
|
||||||
|
current_row = self._meta_get_current()
|
||||||
|
if current_row is not None:
|
||||||
|
start = current_row + 1
|
||||||
|
else:
|
||||||
|
start = 0
|
||||||
|
notes_rows = self._meta_get_notes()
|
||||||
|
for row in range(start, self.rowCount()):
|
||||||
|
if row in notes_rows:
|
||||||
|
continue
|
||||||
|
self._meta_set_next(row)
|
||||||
|
break
|
||||||
|
|
||||||
|
self._repaint()
|
||||||
|
|
||||||
|
track_id = self._get_row_id(row)
|
||||||
|
return track_id
|
||||||
|
|
||||||
def _meta_clear(self, row):
|
def _meta_clear(self, row):
|
||||||
"Clear metadata for row"
|
"Clear metadata for row"
|
||||||
|
|
||||||
@ -711,26 +552,27 @@ class Playlist(QTableWidget):
|
|||||||
title = ""
|
title = ""
|
||||||
DEBUG(f"_meta_set(row={row}, title={title}, metadata={metadata})")
|
DEBUG(f"_meta_set(row={row}, title={title}, metadata={metadata})")
|
||||||
if row is None:
|
if row is None:
|
||||||
raise ValueError(f"_meta_set() with row=None")
|
raise ValueError("_meta_set() with row=None")
|
||||||
|
|
||||||
self.item(row, self.COL_INDEX).setData(Qt.UserRole, metadata)
|
self.item(row, self.COL_INDEX).setData(Qt.UserRole, metadata)
|
||||||
|
|
||||||
def _set_next(self, row):
|
def _set_next(self, row):
|
||||||
"""
|
"""
|
||||||
If passed row is track row, set that track as the next track to
|
If passed row is track row, set that track as the next track to
|
||||||
be played and return True. Otherwise return False.
|
be played and return track_id. Otherwise return None.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
DEBUG(f"_set_next({row})")
|
DEBUG(f"_set_next({row})")
|
||||||
|
|
||||||
if row in self._meta_get_notes():
|
if row in self._meta_get_notes():
|
||||||
return False
|
return None
|
||||||
|
|
||||||
if self.item(row, self.COL_INDEX):
|
if self.item(row, self.COL_INDEX):
|
||||||
self._cue_next_track(row)
|
self._meta_set_next(row)
|
||||||
return True
|
self._repaint()
|
||||||
|
return self._get_row_id(row)
|
||||||
return False
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
def _repaint(self, clear_selection=True):
|
def _repaint(self, clear_selection=True):
|
||||||
"Set row colours, fonts, etc, and save playlist"
|
"Set row colours, fonts, etc, and save playlist"
|
||||||
@ -764,10 +606,10 @@ class Playlist(QTableWidget):
|
|||||||
|
|
||||||
elif row == current:
|
elif row == current:
|
||||||
# Set start time
|
# Set start time
|
||||||
self._set_row_time(row, self.current_track.start_time)
|
self._set_row_time(row, self.current_track_start_time)
|
||||||
# Calculate next_start_time
|
# Calculate next_start_time
|
||||||
next_start_time = self._calculate_next_start_time(
|
next_start_time = self._calculate_next_start_time(
|
||||||
row, self.current_track.start_time)
|
row, self.current_track_start_time)
|
||||||
# Set colour
|
# Set colour
|
||||||
self._set_row_colour(row, QColor(
|
self._set_row_colour(row, QColor(
|
||||||
Config.COLOUR_CURRENT_PLAYLIST))
|
Config.COLOUR_CURRENT_PLAYLIST))
|
||||||
@ -775,10 +617,10 @@ class Playlist(QTableWidget):
|
|||||||
self._set_row_bold(row)
|
self._set_row_bold(row)
|
||||||
|
|
||||||
elif row == next:
|
elif row == next:
|
||||||
# if there's a current row, set start time from that
|
# if there's a current track playing, set start time from that
|
||||||
if self.current_track:
|
if self.current_track_start_time:
|
||||||
start_time = self._calculate_next_start_time(
|
start_time = self._calculate_next_start_time(
|
||||||
current, self.current_track.start_time)
|
current, self.current_track_start_time)
|
||||||
else:
|
else:
|
||||||
# No current track to base from, but don't change
|
# No current track to base from, but don't change
|
||||||
# time if it's already set
|
# time if it's already set
|
||||||
@ -813,9 +655,6 @@ class Playlist(QTableWidget):
|
|||||||
# Don't dim unplayed tracks
|
# Don't dim unplayed tracks
|
||||||
self._set_row_bold(row)
|
self._set_row_bold(row)
|
||||||
|
|
||||||
# Headers might need updating
|
|
||||||
# TODO self.parent().parent().update_headers()
|
|
||||||
|
|
||||||
def _save_playlist(self):
|
def _save_playlist(self):
|
||||||
"""
|
"""
|
||||||
Save playlist to database. We do this by correcting differences
|
Save playlist to database. We do this by correcting differences
|
||||||
@ -827,7 +666,7 @@ class Playlist(QTableWidget):
|
|||||||
times in one playlist and in multiple playlists.
|
times in one playlist and in multiple playlists.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
playlist = Playlists.get_playlist_by_id(self.playlist_id)
|
playlist = Playlists.get_playlist_by_id(self.id)
|
||||||
|
|
||||||
# Notes first
|
# Notes first
|
||||||
# Create dictionaries indexed by note_id
|
# Create dictionaries indexed by note_id
|
||||||
@ -894,8 +733,7 @@ class Playlist(QTableWidget):
|
|||||||
set(set(playlist_tracks.keys()) - set(database_tracks.keys()))
|
set(set(playlist_tracks.keys()) - set(database_tracks.keys()))
|
||||||
):
|
):
|
||||||
DEBUG(f"_save_playlist(): row {row} missing from database")
|
DEBUG(f"_save_playlist(): row {row} missing from database")
|
||||||
PlaylistTracks.add_track(self.playlist_id, playlist_tracks[row],
|
PlaylistTracks.add_track(self.id, playlist_tracks[row], row)
|
||||||
row)
|
|
||||||
|
|
||||||
# Track rows to remove from database
|
# Track rows to remove from database
|
||||||
for row in (
|
for row in (
|
||||||
@ -919,7 +757,21 @@ class Playlist(QTableWidget):
|
|||||||
f"to track={playlist_tracks[row]}"
|
f"to track={playlist_tracks[row]}"
|
||||||
)
|
)
|
||||||
PlaylistTracks.update_row_track(
|
PlaylistTracks.update_row_track(
|
||||||
self.playlist_id, row, playlist_tracks[row])
|
self.id, row, playlist_tracks[row])
|
||||||
|
|
||||||
|
def _set_column_widths(self):
|
||||||
|
|
||||||
|
# Column widths from settings
|
||||||
|
for column in range(self.columnCount()):
|
||||||
|
# Only show column 0 in test mode
|
||||||
|
if (column == 0 and not Config.TESTMODE):
|
||||||
|
self.setColumnWidth(0, 0)
|
||||||
|
else:
|
||||||
|
name = f"playlist_col_{str(column)}_width"
|
||||||
|
record = Settings.get_int(name)
|
||||||
|
if record.f_int is not None:
|
||||||
|
print("setting column width")
|
||||||
|
self.setColumnWidth(column, record.f_int)
|
||||||
|
|
||||||
def _set_row_bold(self, row, bold=True):
|
def _set_row_bold(self, row, bold=True):
|
||||||
boldfont = QFont()
|
boldfont = QFont()
|
||||||
@ -943,134 +795,3 @@ class Playlist(QTableWidget):
|
|||||||
time_str = ""
|
time_str = ""
|
||||||
item = QTableWidgetItem(time_str)
|
item = QTableWidgetItem(time_str)
|
||||||
self.setItem(row, self.COL_START_TIME, item)
|
self.setItem(row, self.COL_START_TIME, item)
|
||||||
|
|
||||||
|
|
||||||
class DbDialog(QDialog):
|
|
||||||
def __init__(self, parent=None):
|
|
||||||
super().__init__(parent)
|
|
||||||
self.ui = Ui_Dialog()
|
|
||||||
self.ui.setupUi(self)
|
|
||||||
self.ui.searchString.textEdited.connect(self.chars_typed)
|
|
||||||
self.ui.matchList.itemDoubleClicked.connect(self.double_click)
|
|
||||||
self.ui.btnAdd.clicked.connect(self.add_selected)
|
|
||||||
self.ui.btnAddClose.clicked.connect(self.add_selected_and_close)
|
|
||||||
self.ui.btnClose.clicked.connect(self.close)
|
|
||||||
|
|
||||||
record = Settings.get_int("dbdialog_width")
|
|
||||||
width = record.f_int or 800
|
|
||||||
record = Settings.get_int("dbdialog_height")
|
|
||||||
height = record.f_int or 600
|
|
||||||
self.resize(width, height)
|
|
||||||
|
|
||||||
def __del__(self):
|
|
||||||
record = Settings.get_int("dbdialog_height")
|
|
||||||
if record.f_int != self.height():
|
|
||||||
record.update({'f_int': self.height()})
|
|
||||||
|
|
||||||
record = Settings.get_int("dbdialog_width")
|
|
||||||
if record.f_int != self.width():
|
|
||||||
record.update({'f_int': self.width()})
|
|
||||||
|
|
||||||
def add_selected(self):
|
|
||||||
if not self.ui.matchList.selectedItems():
|
|
||||||
return
|
|
||||||
|
|
||||||
item = self.ui.matchList.currentItem()
|
|
||||||
track_id = item.data(Qt.UserRole)
|
|
||||||
self.add_track(track_id)
|
|
||||||
|
|
||||||
def add_selected_and_close(self):
|
|
||||||
self.add_selected()
|
|
||||||
self.close()
|
|
||||||
|
|
||||||
def chars_typed(self, s):
|
|
||||||
if len(s) >= 3:
|
|
||||||
matches = Tracks.search_titles(s)
|
|
||||||
self.ui.matchList.clear()
|
|
||||||
if matches:
|
|
||||||
for track in matches:
|
|
||||||
t = QListWidgetItem()
|
|
||||||
t.setText(
|
|
||||||
f"{track.title} - {track.artist} "
|
|
||||||
f"[{helpers.ms_to_mmss(track.duration)}]"
|
|
||||||
)
|
|
||||||
t.setData(Qt.UserRole, track.id)
|
|
||||||
self.ui.matchList.addItem(t)
|
|
||||||
|
|
||||||
def double_click(self, entry):
|
|
||||||
track_id = entry.data(Qt.UserRole)
|
|
||||||
self.add_track(track_id)
|
|
||||||
# Select search text to make it easier for next search
|
|
||||||
self.select_searchtext()
|
|
||||||
|
|
||||||
def add_track(self, track_id):
|
|
||||||
track = Tracks.track_from_id(track_id)
|
|
||||||
self.parent().add_to_playlist(track)
|
|
||||||
# Select search text to make it easier for next search
|
|
||||||
self.select_searchtext()
|
|
||||||
|
|
||||||
def select_searchtext(self):
|
|
||||||
self.ui.searchString.selectAll()
|
|
||||||
self.ui.searchString.setFocus()
|
|
||||||
|
|
||||||
|
|
||||||
class SelectPlaylistDialog(QDialog):
|
|
||||||
def __init__(self, parent=None):
|
|
||||||
super().__init__(parent)
|
|
||||||
self.ui = Ui_dlgSelectPlaylist()
|
|
||||||
self.ui.setupUi(self)
|
|
||||||
self.ui.lstPlaylists.itemDoubleClicked.connect(self.list_doubleclick)
|
|
||||||
self.ui.buttonBox.accepted.connect(self.open)
|
|
||||||
self.ui.buttonBox.rejected.connect(self.close)
|
|
||||||
|
|
||||||
for (plid, plname) in [
|
|
||||||
(a.id, a.name) for a in Playlists.get_all_playlists()
|
|
||||||
]:
|
|
||||||
p = QListWidgetItem()
|
|
||||||
p.setText(plname)
|
|
||||||
p.setData(Qt.UserRole, plid)
|
|
||||||
self.ui.lstPlaylists.addItem(p)
|
|
||||||
|
|
||||||
def list_doubleclick(self, entry):
|
|
||||||
plid = entry.data(Qt.UserRole)
|
|
||||||
self.parent().load_playlist(plid)
|
|
||||||
self.close()
|
|
||||||
|
|
||||||
def open(self):
|
|
||||||
if self.ui.lstPlaylists.selectedItems():
|
|
||||||
item = self.ui.lstPlaylists.currentItem()
|
|
||||||
plid = item.data(Qt.UserRole)
|
|
||||||
self.parent().load_playlist(plid)
|
|
||||||
self.close()
|
|
||||||
|
|
||||||
|
|
||||||
class Window(QWidget):
|
|
||||||
def __init__(self):
|
|
||||||
super(Window, self).__init__()
|
|
||||||
|
|
||||||
layout = QHBoxLayout()
|
|
||||||
self.setLayout(layout)
|
|
||||||
|
|
||||||
self.table_widget = Playlist()
|
|
||||||
layout.addWidget(self.table_widget)
|
|
||||||
|
|
||||||
# setup table widget
|
|
||||||
self.table_widget.setColumnCount(2)
|
|
||||||
self.table_widget.setHorizontalHeaderLabels(['Type', 'Name'])
|
|
||||||
|
|
||||||
items = [('Red', 'Toyota'), ('Blue', 'RV'), ('Green', 'Beetle'),
|
|
||||||
('Silver', 'Chevy'), ('Black', 'BMW')]
|
|
||||||
self.table_widget.setRowCount(len(items))
|
|
||||||
for i, (color, model) in enumerate(items):
|
|
||||||
self.table_widget.setItem(i, 0, QTableWidgetItem(color))
|
|
||||||
self.table_widget.setItem(i, 1, QTableWidgetItem(model))
|
|
||||||
|
|
||||||
self.resize(400, 400)
|
|
||||||
self.show()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
import sys
|
|
||||||
app = QApplication(sys.argv)
|
|
||||||
window = Window()
|
|
||||||
sys.exit(app.exec_())
|
|
||||||
|
|||||||
@ -123,7 +123,7 @@ border: 1px solid rgb(85, 87, 83);</string>
|
|||||||
<item>
|
<item>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout">
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLabel" name="previous_track">
|
<widget class="QLabel" name="hdrPreviousTrack">
|
||||||
<property name="minimumSize">
|
<property name="minimumSize">
|
||||||
<size>
|
<size>
|
||||||
<width>16</width>
|
<width>16</width>
|
||||||
@ -152,7 +152,7 @@ border: 1px solid rgb(85, 87, 83);</string>
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLabel" name="current_track">
|
<widget class="QLabel" name="hdrCurrentTrack">
|
||||||
<property name="font">
|
<property name="font">
|
||||||
<font>
|
<font>
|
||||||
<family>Sans</family>
|
<family>Sans</family>
|
||||||
@ -169,7 +169,7 @@ border: 1px solid rgb(85, 87, 83);</string>
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLabel" name="next_track">
|
<widget class="QLabel" name="hdrNextTrack">
|
||||||
<property name="font">
|
<property name="font">
|
||||||
<font>
|
<font>
|
||||||
<family>Sans</family>
|
<family>Sans</family>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user