Compare commits

...

3 Commits

Author SHA1 Message Date
Keith Edmunds
ad717aeb2c Fix error inserting note (and track?) above selected row 2021-06-02 08:27:45 +01:00
Keith Edmunds
4984ddec98 Use sessions correctly (fixes #5) 2021-06-02 08:26:54 +01:00
Keith Edmunds
caa13b6693 Update database when playlist closed. Fixes #8 2021-06-01 21:20:32 +01:00
4 changed files with 339 additions and 301 deletions

View File

@ -31,9 +31,8 @@ engine = sqlalchemy.create_engine(f"{Config.MYSQL_CONNECT}?charset=utf8",
Base = declarative_base() Base = declarative_base()
Base.metadata.create_all(engine) Base.metadata.create_all(engine)
# Create a Session # Create a Session factory
Session = sessionmaker(bind=engine) Session = sessionmaker(bind=engine)
session = Session()
# Database classes # Database classes
@ -52,7 +51,7 @@ class Notes(Base):
) )
@staticmethod @staticmethod
def add_note(playlist_id, row, text): def add_note(session, playlist_id, row, text):
DEBUG(f"add_note(playlist_id={playlist_id}, row={row}, text={text})") DEBUG(f"add_note(playlist_id={playlist_id}, row={row}, text={text})")
note = Notes() note = Notes()
note.playlist_id = playlist_id note.playlist_id = playlist_id
@ -63,18 +62,19 @@ class Notes(Base):
return note return note
@staticmethod @staticmethod
def delete_note(id): def delete_note(session, id):
DEBUG(f"delete_note(id={id}") DEBUG(f"delete_note(id={id}")
session.query(Notes).filter(Notes.id == id).delete() session.query(Notes).filter(Notes.id == id).delete()
session.commit() session.commit()
@staticmethod # Not currently used 1 June 2021
def get_note(id): # @staticmethod
return session.query(Notes).filter(Notes.id == id).one() # def get_note(session, id):
# return session.query(Notes).filter(Notes.id == id).one()
@classmethod @classmethod
def update_note(cls, id, row, text=None): def update_note(cls, session, id, row, text=None):
""" """
Update note details. If text=None, don't change text. Update note details. If text=None, don't change text.
""" """
@ -97,7 +97,7 @@ class Playdates(Base):
tracks = relationship("Tracks", back_populates="playdates") tracks = relationship("Tracks", back_populates="playdates")
@staticmethod @staticmethod
def add_playdate(track): def add_playdate(session, track):
DEBUG(f"add_playdate(track={track})") DEBUG(f"add_playdate(track={track})")
pd = Playdates() pd = Playdates()
pd.lastplayed = datetime.now() pd.lastplayed = datetime.now()
@ -167,7 +167,7 @@ class Playlists(Base):
return (f"<Playlist(id={self.id}, name={self.name}>") return (f"<Playlist(id={self.id}, name={self.name}>")
@staticmethod @staticmethod
def new(name): def new(session, name):
DEBUG(f"Playlists.new(name={name})") DEBUG(f"Playlists.new(name={name})")
playlist = Playlists() playlist = Playlists()
playlist.name = name playlist.name = name
@ -176,7 +176,7 @@ class Playlists(Base):
return playlist return playlist
@staticmethod @staticmethod
def open(plid): def open(session, plid):
"Record playlist as loaded and used now" "Record playlist as loaded and used now"
p = session.query(Playlists).filter(Playlists.id == plid).one() p = session.query(Playlists).filter(Playlists.id == plid).one()
@ -186,14 +186,15 @@ class Playlists(Base):
return p return p
def close(self): def close(self, session):
"Record playlist as no longer loaded" "Record playlist as no longer loaded"
self.loaded = False self.loaded = False
session.add(self)
session.commit() session.commit()
@staticmethod @staticmethod
def get_last_used(): def get_last_used(session):
""" """
Return a list of playlists marked "loaded", ordered by loaded date. Return a list of playlists marked "loaded", ordered by loaded date.
""" """
@ -205,13 +206,13 @@ class Playlists(Base):
).all() ).all()
@staticmethod @staticmethod
def get_all_playlists(): def get_all_playlists(session):
"Returns a list of all playlists" "Returns a list of all playlists"
return session.query(Playlists).all() return session.query(Playlists).all()
@staticmethod @staticmethod
def get_all_closed_playlists(): def get_all_closed_playlists(session):
"Returns a list of all playlists not currently open" "Returns a list of all playlists not currently open"
return ( return (
@ -223,25 +224,26 @@ class Playlists(Base):
.order_by(Playlists.last_used.desc()) .order_by(Playlists.last_used.desc())
).all() ).all()
@staticmethod # Not currently used 1 June 2021
def get_name(plid): # @staticmethod
""" # def get_name(session, plid):
Return name of playlist with id 'plid' # """
""" # Return name of playlist with id 'plid'
# """
return ( # return (
session.query(Playlists.name) # session.query(Playlists.name)
.filter(Playlists.id == plid) # .filter(Playlists.id == plid)
).one()[0] # ).one()[0]
def add_track(self, track, row=None): def add_track(self, session, track, row=None):
""" """
Add track to playlist at given row. Add track to playlist at given row.
If row=None, add to end of playlist If row=None, add to end of playlist
""" """
if not row: if not row:
row = PlaylistTracks.new_row(self.id) row = PlaylistTracks.new_row(session, self.id)
glue = PlaylistTracks(row=row) glue = PlaylistTracks(row=row)
glue.track_id = track.id glue.track_id = track.id
@ -265,14 +267,14 @@ class PlaylistTracks(Base):
playlists = relationship("Playlists", back_populates="tracks") playlists = relationship("Playlists", back_populates="tracks")
@staticmethod @staticmethod
def new_row(playlist_id): def new_row(session, playlist_id):
"Return row number > largest existing row number" "Return row number > largest existing row number"
last_row = session.query(func.max(PlaylistTracks.row)).one()[0] last_row = session.query(func.max(PlaylistTracks.row)).one()[0]
return last_row + 1 return last_row + 1
@staticmethod @staticmethod
def add_track(playlist_id, track_id, row): def add_track(session, playlist_id, track_id, row):
DEBUG( DEBUG(
f"PlaylistTracks.add_track(playlist_id={playlist_id}, " f"PlaylistTracks.add_track(playlist_id={playlist_id}, "
f"track_id={track_id}, row={row})" f"track_id={track_id}, row={row})"
@ -285,7 +287,7 @@ class PlaylistTracks(Base):
session.commit() session.commit()
@staticmethod @staticmethod
def move_track(from_playlist_id, row, to_playlist_id): def move_track(session, from_playlist_id, row, to_playlist_id):
DEBUG( DEBUG(
"PlaylistTracks.move_tracks(from_playlist_id=" "PlaylistTracks.move_tracks(from_playlist_id="
f"{from_playlist_id}, row={row}, " f"{from_playlist_id}, row={row}, "
@ -304,7 +306,7 @@ class PlaylistTracks(Base):
session.commit() session.commit()
@staticmethod @staticmethod
def remove_track(playlist_id, row): def remove_track(session, playlist_id, row):
DEBUG( DEBUG(
f"PlaylistTracks.remove_track(playlist_id={playlist_id}, " f"PlaylistTracks.remove_track(playlist_id={playlist_id}, "
f"row={row})" f"row={row})"
@ -316,7 +318,7 @@ class PlaylistTracks(Base):
session.commit() session.commit()
@staticmethod @staticmethod
def update_row_track(playlist_id, row, track_id): def update_row_track(session, playlist_id, row, track_id):
DEBUG( DEBUG(
f"PlaylistTracks.update_track_row(playlist_id={playlist_id}, " f"PlaylistTracks.update_track_row(playlist_id={playlist_id}, "
f"row={row}, track_id={track_id})" f"row={row}, track_id={track_id})"
@ -334,6 +336,13 @@ class PlaylistTracks(Base):
f"PlaylistTracks.row == {row}" f"PlaylistTracks.row == {row}"
) )
return return
except NoResultFound:
ERROR(
f"No rows matched in query: "
f"PlaylistTracks.playlist_id == {playlist_id}, "
f"PlaylistTracks.row == {row}"
)
return
plt.track_id = track_id plt.track_id = track_id
session.commit() session.commit()
@ -349,7 +358,7 @@ class Settings(Base):
f_string = Column(String(128), default=None, nullable=True) f_string = Column(String(128), default=None, nullable=True)
@classmethod @classmethod
def get_int(cls, name): def get_int(cls, session, name):
try: try:
int_setting = session.query(cls).filter( int_setting = session.query(cls).filter(
cls.name == name).one() cls.name == name).one()
@ -361,7 +370,7 @@ class Settings(Base):
session.commit() session.commit()
return int_setting return int_setting
def update(self, data): def update(self, session, data):
for key, value in data.items(): for key, value in data.items():
assert hasattr(self, key) assert hasattr(self, key)
setattr(self, key, value) setattr(self, key, value)
@ -391,7 +400,7 @@ class Tracks(Base):
) )
@classmethod @classmethod
def get_or_create(cls, path): def get_or_create(cls, session, path):
DEBUG(f"Tracks.get_or_create(path={path})") DEBUG(f"Tracks.get_or_create(path={path})")
try: try:
track = session.query(cls).filter(cls.path == path).one() track = session.query(cls).filter(cls.path == path).one()
@ -402,7 +411,7 @@ class Tracks(Base):
return track return track
@staticmethod @staticmethod
def get_duration(id): def get_duration(session, id):
try: try:
return session.query( return session.query(
Tracks.duration).filter(Tracks.id == id).one()[0] Tracks.duration).filter(Tracks.id == id).one()[0]
@ -411,19 +420,20 @@ class Tracks(Base):
return None return None
@staticmethod @staticmethod
def get_all_paths(): def get_all_paths(session):
"Return a list of paths of all tracks" "Return a list of paths of all tracks"
return [a[0] for a in session.query(Tracks.path).all()] return [a[0] for a in session.query(Tracks.path).all()]
@classmethod # Not used as of 1 June 2021
def get_all_tracks(cls): # @classmethod
"Return a list of all tracks" # def get_all_tracks(cls):
# "Return a list of all tracks"
return session.query(cls).all() # return session.query(cls).all()
@staticmethod @staticmethod
def get_path(id): def get_path(session, id):
try: try:
return session.query(Tracks.path).filter(Tracks.id == id).one()[0] return session.query(Tracks.path).filter(Tracks.id == id).one()[0]
except NoResultFound: except NoResultFound:
@ -431,7 +441,7 @@ class Tracks(Base):
return None return None
@staticmethod @staticmethod
def get_track(id): def get_track(session, id):
try: try:
DEBUG(f"Tracks.get_track(track_id={id})") DEBUG(f"Tracks.get_track(track_id={id})")
track = session.query(Tracks).filter(Tracks.id == id).one() track = session.query(Tracks).filter(Tracks.id == id).one()
@ -441,7 +451,7 @@ class Tracks(Base):
return None return None
@staticmethod @staticmethod
def search_titles(text): def search_titles(session, text):
return ( return (
session.query(Tracks) session.query(Tracks)
.filter(Tracks.title.ilike(f"%{text}%")) .filter(Tracks.title.ilike(f"%{text}%"))
@ -449,7 +459,7 @@ class Tracks(Base):
).all() ).all()
@staticmethod @staticmethod
def track_from_id(id): def track_from_id(session, id):
return session.query(Tracks).filter( return session.query(Tracks).filter(
Tracks.id == id).one() Tracks.id == id).one()

View File

@ -25,7 +25,8 @@ import helpers
import music import music
from config import Config from config import Config
from model import Notes, Playdates, Playlists, PlaylistTracks, Settings, Tracks from model import (Notes, Playdates, Playlists, PlaylistTracks,
Session, Settings, Tracks)
from playlists import Playlist from playlists import Playlist
from songdb import add_path_to_db from songdb import add_path_to_db
from ui.dlg_search_database_ui import Ui_Dialog from ui.dlg_search_database_ui import Ui_Dialog
@ -70,19 +71,21 @@ class Window(QMainWindow, Ui_MainWindow):
dlg.setNameFilter("Music files (*.flac *.mp3)") dlg.setNameFilter("Music files (*.flac *.mp3)")
if dlg.exec_(): if dlg.exec_():
with Session() as session:
for fname in dlg.selectedFiles(): for fname in dlg.selectedFiles():
track = add_path_to_db(fname) track = add_path_to_db(session, fname)
self.visible_playlist()._add_to_playlist(track) self.visible_playlist()._add_to_playlist(track)
def set_main_window_size(self): def set_main_window_size(self):
record = Settings.get_int("mainwindow_x") with Session() as session:
record = Settings.get_int(session, "mainwindow_x")
x = record.f_int or 1 x = record.f_int or 1
record = Settings.get_int("mainwindow_y") record = Settings.get_int(session, "mainwindow_y")
y = record.f_int or 1 y = record.f_int or 1
record = Settings.get_int("mainwindow_width") record = Settings.get_int(session, "mainwindow_width")
width = record.f_int or 1599 width = record.f_int or 1599
record = Settings.get_int("mainwindow_height") record = Settings.get_int(session, "mainwindow_height")
height = record.f_int or 981 height = record.f_int or 981
self.setGeometry(x, y, width, height) self.setGeometry(x, y, width, height)
@ -100,21 +103,22 @@ class Window(QMainWindow, Ui_MainWindow):
else: else:
DEBUG("closeEvent() accepted") DEBUG("closeEvent() accepted")
record = Settings.get_int("mainwindow_height") with Session() as session:
record = Settings.get_int(session, "mainwindow_height")
if record.f_int != self.height(): if record.f_int != self.height():
record.update({'f_int': self.height()}) record.update(session, {'f_int': self.height()})
record = Settings.get_int("mainwindow_width") record = Settings.get_int(session, "mainwindow_width")
if record.f_int != self.width(): if record.f_int != self.width():
record.update({'f_int': self.width()}) record.update(session, {'f_int': self.width()})
record = Settings.get_int("mainwindow_x") record = Settings.get_int(session, "mainwindow_x")
if record.f_int != self.x(): if record.f_int != self.x():
record.update({'f_int': self.x()}) record.update(session, {'f_int': self.x()})
record = Settings.get_int("mainwindow_y") record = Settings.get_int(session, "mainwindow_y")
if record.f_int != self.y(): if record.f_int != self.y():
record.update({'f_int': self.y()}) record.update(session, {'f_int': self.y()})
event.accept() event.accept()
@ -159,8 +163,9 @@ class Window(QMainWindow, Ui_MainWindow):
dlg.resize(500, 100) dlg.resize(500, 100)
ok = dlg.exec() ok = dlg.exec()
if ok: if ok:
playlist = Playlists.new(dlg.textValue()) with Session() as session:
self.load_playlist(playlist) playlist = Playlists.new(session, dlg.textValue())
self.load_playlist(session, playlist)
def change_volume(self, volume): def change_volume(self, volume):
"Change player maximum volume" "Change player maximum volume"
@ -170,11 +175,12 @@ class Window(QMainWindow, Ui_MainWindow):
self.music.set_volume(volume) self.music.set_volume(volume)
def close_playlist(self): def close_playlist(self):
self.visible_playlist().db.close() with Session() as session:
self.visible_playlist().db.close(session)
index = self.tabPlaylist.currentIndex() index = self.tabPlaylist.currentIndex()
self.tabPlaylist.removeTab(index) self.tabPlaylist.removeTab(index)
def create_note(self, text): def create_note(self, session, text):
""" """
Create note Create note
@ -189,7 +195,10 @@ class Window(QMainWindow, Ui_MainWindow):
row = self.visible_playlist().rowCount() row = self.visible_playlist().rowCount()
DEBUG(f"musicmuster.create_note(text={text}): row={row}") DEBUG(f"musicmuster.create_note(text={text}): row={row}")
return Notes.add_note(self.visible_playlist().db.id, row, text) note = Notes.add_note(
session, self.visible_playlist().db.id, row, text)
return note
def disable_play_next_controls(self): def disable_play_next_controls(self):
DEBUG("disable_play_next_controls()") DEBUG("disable_play_next_controls()")
@ -250,19 +259,21 @@ class Window(QMainWindow, Ui_MainWindow):
dlg.resize(500, 100) dlg.resize(500, 100)
ok = dlg.exec() ok = dlg.exec()
if ok: if ok:
note = self.create_note(dlg.textValue()) with Session() as session:
note = self.create_note(session, dlg.textValue())
self.visible_playlist().add_note(note) self.visible_playlist().add_note(note)
def load_last_playlists(self): def load_last_playlists(self):
"Load the playlists that we loaded at end of last session" "Load the playlists that we loaded at end of last session"
for playlist in Playlists.get_last_used(): with Session() as session:
for playlist in Playlists.get_last_used(session):
DEBUG( DEBUG(
f"load_last_playlists(), playlist.name={playlist.name}, " f"load_last_playlists(), playlist.name={playlist.name}, "
f"playlist.id={playlist.id}") f"playlist.id={playlist.id}")
self.load_playlist(playlist) self.load_playlist(session, playlist)
def load_playlist(self, playlist_db): def load_playlist(self, session, playlist_db):
""" """
Take the passed database object, create a playlist display, attach Take the passed database object, create a playlist display, attach
the database object, get it populated and then add tab. the database object, get it populated and then add tab.
@ -270,15 +281,17 @@ class Window(QMainWindow, Ui_MainWindow):
playlist_table = Playlist(self) playlist_table = Playlist(self)
playlist_table.db = playlist_db playlist_table.db = playlist_db
playlist_table.populate() playlist_table.populate(session)
idx = self.tabPlaylist.addTab(playlist_table, playlist_db.name) idx = self.tabPlaylist.addTab(playlist_table, playlist_db.name)
self.tabPlaylist.setCurrentIndex(idx) self.tabPlaylist.setCurrentIndex(idx)
def move_selected(self): def move_selected(self):
"Move selected rows to another playlist" "Move selected rows to another playlist"
with Session() as session:
playlists = list( playlists = list(
set(Playlists.get_all_playlists()) - {self.visible_playlist().db} set(Playlists.get_all_playlists(session)) -
{self.visible_playlist().db}
) )
dlg = SelectPlaylistDialog(self, playlists=playlists) dlg = SelectPlaylistDialog(self, playlists=playlists)
dlg.exec() dlg.exec()
@ -300,10 +313,11 @@ class Window(QMainWindow, Ui_MainWindow):
rows.append(row) rows.append(row)
# Update database # Update database
PlaylistTracks.move_track( PlaylistTracks.move_track(
self.visible_playlist().db.id, row, dlg.plid) session, self.visible_playlist().db.id, row, dlg.plid)
# Update destination playlist if visible # Update destination playlist if visible
if destination_playlist: if destination_playlist:
destination_playlist.add_track(Tracks.track_from_id(track_id)) destination_playlist.add_track(
session, Tracks.track_from_id(session, track_id))
# Update source playlist # Update source playlist
self.visible_playlist().remove_rows(rows) self.visible_playlist().remove_rows(rows)
@ -334,6 +348,7 @@ class Window(QMainWindow, Ui_MainWindow):
f"{self.current_track.title if self.current_track else None}" f"{self.current_track.title if self.current_track else None}"
) )
with Session() as session:
# Stop current track, if any # Stop current track, if any
self.stop_playing() self.stop_playing()
@ -348,7 +363,7 @@ class Window(QMainWindow, Ui_MainWindow):
next_track_id = self.current_track_playlist.play_started() next_track_id = self.current_track_playlist.play_started()
if next_track_id is not None: if next_track_id is not None:
self.next_track = Tracks.get_track(next_track_id) self.next_track = Tracks.get_track(session, next_track_id)
self.next_track_playlist = self.current_track_playlist self.next_track_playlist = self.current_track_playlist
# Check we can read it # Check we can read it
if not os.access(self.next_track.path, os.R_OK): if not os.access(self.next_track.path, os.R_OK):
@ -360,7 +375,7 @@ class Window(QMainWindow, Ui_MainWindow):
# Tell database to record it as played # Tell database to record it as played
self.current_track.update_lastplayed() self.current_track.update_lastplayed()
Playdates.add_playdate(self.current_track) Playdates.add_playdate(session, self.current_track)
self.disable_play_next_controls() self.disable_play_next_controls()
self.update_headers() self.update_headers()
@ -386,12 +401,13 @@ class Window(QMainWindow, Ui_MainWindow):
dlg.exec() dlg.exec()
def open_playlist(self): def open_playlist(self):
playlists = Playlists.get_all_closed_playlists() with Session() as session:
playlists = Playlists.get_all_closed_playlists(session)
dlg = SelectPlaylistDialog(self, playlists=playlists) dlg = SelectPlaylistDialog(self, playlists=playlists)
dlg.exec() dlg.exec()
if dlg.plid: if dlg.plid:
playlist = Playlists.open(dlg.plid) playlist = Playlists.open(session, dlg.plid)
self.load_playlist(playlist) self.load_playlist(session, playlist)
def select_next_track(self): def select_next_track(self):
"Select next or first track in playlist" "Select next or first track in playlist"
@ -406,6 +422,7 @@ class Window(QMainWindow, Ui_MainWindow):
def set_next_track(self, next_track_id=None): def set_next_track(self, next_track_id=None):
"Set selected track as next" "Set selected track as next"
with Session() as session:
if not next_track_id: if not next_track_id:
next_track_id = self.visible_playlist().set_selected_as_next() next_track_id = self.visible_playlist().set_selected_as_next()
if next_track_id: if next_track_id:
@ -413,7 +430,7 @@ class Window(QMainWindow, Ui_MainWindow):
if self.next_track_playlist: if self.next_track_playlist:
self.next_track_playlist.clear_next() self.next_track_playlist.clear_next()
self.next_track_playlist = self.visible_playlist() self.next_track_playlist = self.visible_playlist()
self.next_track = Tracks.get_track(next_track_id) self.next_track = Tracks.get_track(session, next_track_id)
self.update_headers() self.update_headers()
def show_warning(self, title, msg): def show_warning(self, title, msg):
@ -605,6 +622,7 @@ class Window(QMainWindow, Ui_MainWindow):
class DbDialog(QDialog): class DbDialog(QDialog):
def __init__(self, parent=None): def __init__(self, parent=None):
super().__init__(parent) super().__init__(parent)
self.session = Session()
self.ui = Ui_Dialog() self.ui = Ui_Dialog()
self.ui.setupUi(self) self.ui.setupUi(self)
self.ui.searchString.textEdited.connect(self.chars_typed) self.ui.searchString.textEdited.connect(self.chars_typed)
@ -614,20 +632,20 @@ class DbDialog(QDialog):
self.ui.btnClose.clicked.connect(self.close) self.ui.btnClose.clicked.connect(self.close)
self.ui.matchList.itemSelectionChanged.connect(self.selection_changed) self.ui.matchList.itemSelectionChanged.connect(self.selection_changed)
record = Settings.get_int("dbdialog_width") record = Settings.get_int(self.session, "dbdialog_width")
width = record.f_int or 800 width = record.f_int or 800
record = Settings.get_int("dbdialog_height") record = Settings.get_int(self.session, "dbdialog_height")
height = record.f_int or 600 height = record.f_int or 600
self.resize(width, height) self.resize(width, height)
def __del__(self): def __del__(self):
record = Settings.get_int("dbdialog_height") record = Settings.get_int(self.session, "dbdialog_height")
if record.f_int != self.height(): if record.f_int != self.height():
record.update({'f_int': self.height()}) record.update(self.session, {'f_int': self.height()})
record = Settings.get_int("dbdialog_width") record = Settings.get_int(self.session, "dbdialog_width")
if record.f_int != self.width(): if record.f_int != self.width():
record.update({'f_int': self.width()}) record.update(self.session, {'f_int': self.width()})
def add_selected(self): def add_selected(self):
if not self.ui.matchList.selectedItems(): if not self.ui.matchList.selectedItems():
@ -643,7 +661,7 @@ class DbDialog(QDialog):
def chars_typed(self, s): def chars_typed(self, s):
if len(s) >= 3: if len(s) >= 3:
matches = Tracks.search_titles(s) matches = Tracks.search_titles(self.session, s)
self.ui.matchList.clear() self.ui.matchList.clear()
if matches: if matches:
for track in matches: for track in matches:
@ -662,7 +680,7 @@ class DbDialog(QDialog):
self.select_searchtext() self.select_searchtext()
def add_track(self, track_id): def add_track(self, track_id):
track = Tracks.track_from_id(track_id) track = Tracks.track_from_id(self.session, track_id)
self.parent().visible_playlist()._add_to_playlist(track) self.parent().visible_playlist()._add_to_playlist(track)
# Select search text to make it easier for next search # Select search text to make it easier for next search
self.select_searchtext() self.select_searchtext()
@ -677,7 +695,7 @@ class DbDialog(QDialog):
item = self.ui.matchList.currentItem() item = self.ui.matchList.currentItem()
track_id = item.data(Qt.UserRole) track_id = item.data(Qt.UserRole)
self.ui.dbPath.setText(Tracks.get_path(track_id)) self.ui.dbPath.setText(Tracks.get_path(self.session, track_id))
class SelectPlaylistDialog(QDialog): class SelectPlaylistDialog(QDialog):
@ -693,9 +711,10 @@ class SelectPlaylistDialog(QDialog):
self.ui.buttonBox.rejected.connect(self.close) self.ui.buttonBox.rejected.connect(self.close)
self.plid = None self.plid = None
record = Settings.get_int("select_playlist_dialog_width") with Session() as session:
record = Settings.get_int(session, "select_playlist_dialog_width")
width = record.f_int or 800 width = record.f_int or 800
record = Settings.get_int("select_playlist_dialog_height") record = Settings.get_int(session, "select_playlist_dialog_height")
height = record.f_int or 600 height = record.f_int or 600
self.resize(width, height) self.resize(width, height)
@ -706,13 +725,14 @@ class SelectPlaylistDialog(QDialog):
self.ui.lstPlaylists.addItem(p) self.ui.lstPlaylists.addItem(p)
def __del__(self): def __del__(self):
record = Settings.get_int("select_playlist_dialog_height") with Session() as session:
record = Settings.get_int(session, "select_playlist_dialog_height")
if record.f_int != self.height(): if record.f_int != self.height():
record.update({'f_int': self.height()}) record.update(session, {'f_int': self.height()})
record = Settings.get_int("select_playlist_dialog_width") record = Settings.get_int(session, "select_playlist_dialog_width")
if record.f_int != self.width(): if record.f_int != self.width():
record.update({'f_int': self.width()}) record.update(session, {'f_int': self.width()})
def list_doubleclick(self, entry): def list_doubleclick(self, entry):
self.plid = entry.data(Qt.UserRole) self.plid = entry.data(Qt.UserRole)

View File

@ -17,7 +17,7 @@ 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, PlaylistTracks, Settings, Tracks from model import Notes, PlaylistTracks, Session, Settings, Tracks
class Playlist(QTableWidget): class Playlist(QTableWidget):
@ -94,9 +94,10 @@ class Playlist(QTableWidget):
for column in range(self.columnCount()): for column in range(self.columnCount()):
width = self.columnWidth(column) width = self.columnWidth(column)
name = f"playlist_col_{str(column)}_width" name = f"playlist_col_{str(column)}_width"
record = Settings.get_int(name) with Session() as session:
record = Settings.get_int(session, name)
if record.f_int != self.columnWidth(column): if record.f_int != self.columnWidth(column):
record.update({'f_int': width}) record.update(session, {'f_int': width})
event.accept() event.accept()
@ -275,7 +276,7 @@ class Playlist(QTableWidget):
if not self.selectionModel().hasSelection(): if not self.selectionModel().hasSelection():
return None return None
else: else:
return self.selectionModel().selectedRows()[0] return self.selectionModel().selectedRows()[0].row()
def get_selected_rows_and_tracks(self): def get_selected_rows_and_tracks(self):
"Return a list of selected (rows, track_id) tuples" "Return a list of selected (rows, track_id) tuples"
@ -336,7 +337,7 @@ class Playlist(QTableWidget):
self.current_track_start_time = None self.current_track_start_time = None
self._repaint(save_playlist=False) self._repaint(save_playlist=False)
def populate(self): def populate(self, session):
# 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
# on its specified row, only that it will be above # on its specified row, only that it will be above
# larger-numbered row items, and below lower-numbered ones. # larger-numbered row items, and below lower-numbered ones.
@ -454,7 +455,7 @@ class Playlist(QTableWidget):
elif isinstance(data, Notes): elif isinstance(data, Notes):
self.add_note(data, repaint=repaint) self.add_note(data, repaint=repaint)
def _calculate_next_start_time(self, row, start): def _calculate_next_start_time(self, session, row, start):
"Return this row's end time given its start time" "Return this row's end time given its start time"
if start is None: if start is None:
@ -463,7 +464,7 @@ class Playlist(QTableWidget):
DEBUG("_calculate_next_start_time() called with row=None") DEBUG("_calculate_next_start_time() called with row=None")
return None return None
duration = Tracks.get_duration(self._get_row_id(row)) duration = Tracks.get_duration(session, self._get_row_id(row))
return start + timedelta(milliseconds=duration) return start + timedelta(milliseconds=duration)
def _can_read_track(self, track): def _can_read_track(self, track):
@ -499,11 +500,12 @@ class Playlist(QTableWidget):
msg.setDefaultButton(QMessageBox.Cancel) msg.setDefaultButton(QMessageBox.Cancel)
msg.setWindowTitle("Delete row") msg.setWindowTitle("Delete row")
if msg.exec() == QMessageBox.Yes: if msg.exec() == QMessageBox.Yes:
with Session() as session:
id = self._get_row_id(row) id = self._get_row_id(row)
if row in self._meta_get_notes(): if row in self._meta_get_notes():
Notes.delete_note(id) Notes.delete_note(session, id)
else: else:
PlaylistTracks.remove_track(self.db.id, row) PlaylistTracks.remove_track(session, self.db.id, row)
self.removeRow(row) self.removeRow(row)
self._repaint() self._repaint()
@ -723,10 +725,11 @@ class Playlist(QTableWidget):
f"save_playlist={save_playlist})" f"save_playlist={save_playlist})"
) )
with Session() as session:
if clear_selection: if clear_selection:
self.clearSelection() self.clearSelection()
if save_playlist: if save_playlist:
self._save_playlist() self._save_playlist(session)
current = self._meta_get_current() current = self._meta_get_current()
next = self._meta_get_next() next = self._meta_get_next()
@ -756,7 +759,7 @@ class Playlist(QTableWidget):
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) session, 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))
@ -764,10 +767,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 track playing, set start time from that # if there's a track playing, set start time from that
if self.current_track_start_time: 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) session, 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
@ -777,9 +780,10 @@ class Playlist(QTableWidget):
# Now set it # Now set it
self._set_row_time(row, start_time) self._set_row_time(row, start_time)
next_start_time = self._calculate_next_start_time( next_start_time = self._calculate_next_start_time(
row, start_time) session, row, start_time)
# Set colour # Set colour
self._set_row_colour(row, QColor(Config.COLOUR_NEXT_PLAYLIST)) self._set_row_colour(
row, QColor(Config.COLOUR_NEXT_PLAYLIST))
# Make bold # Make bold
self._set_row_bold(row) self._set_row_bold(row)
@ -798,11 +802,11 @@ class Playlist(QTableWidget):
if next_start_time: if next_start_time:
self._set_row_time(row, next_start_time) self._set_row_time(row, next_start_time)
next_start_time = self._calculate_next_start_time( next_start_time = self._calculate_next_start_time(
row, next_start_time) session, row, next_start_time)
# Don't dim unplayed tracks # Don't dim unplayed tracks
self._set_row_bold(row) self._set_row_bold(row)
def _save_playlist(self): def _save_playlist(self, session):
""" """
Save playlist to database. We do this by correcting differences Save playlist to database. We do this by correcting differences
between the on-screen (definitive) playlist and that in the between the on-screen (definitive) playlist and that in the
@ -846,7 +850,7 @@ class Playlist(QTableWidget):
f"_save_playlist(): Delete note note_id={note_id} " f"_save_playlist(): Delete note note_id={note_id} "
f"from playlist {self} in database" f"from playlist {self} in database"
) )
Notes.delete_note(note_id) Notes.delete_note(session, note_id)
# Note rows to update in playlist database # Note rows to update in playlist database
for note_id in set(playlist_notes.keys()) & set(database_notes.keys()): for note_id in set(playlist_notes.keys()) & set(database_notes.keys()):
@ -856,7 +860,7 @@ class Playlist(QTableWidget):
f"from row={database_notes[note_id]} to " f"from row={database_notes[note_id]} to "
f"row={playlist_notes[note_id]}" f"row={playlist_notes[note_id]}"
) )
Notes.update_note(note_id, playlist_notes[note_id]) Notes.update_note(session, note_id, playlist_notes[note_id])
# Now check tracks # Now check tracks
# Create dictionaries indexed by row # Create dictionaries indexed by row
@ -878,7 +882,8 @@ 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.db.id, playlist_tracks[row], row) PlaylistTracks.add_track(session, self.db.id,
playlist_tracks[row], row)
# Track rows to remove from database # Track rows to remove from database
for row in ( for row in (
@ -889,7 +894,7 @@ class Playlist(QTableWidget):
f"_save_playlist(): row {row} in database not playlist " f"_save_playlist(): row {row} in database not playlist "
f"(track={track})" f"(track={track})"
) )
PlaylistTracks.remove_track(self.db.id, row) PlaylistTracks.remove_track(session, self.db.id, row)
# Track rows to update in database # Track rows to update in database
for row in ( for row in (
@ -902,18 +907,19 @@ class Playlist(QTableWidget):
f"to track={playlist_tracks[row]}" f"to track={playlist_tracks[row]}"
) )
PlaylistTracks.update_row_track( PlaylistTracks.update_row_track(
self.db.id, row, playlist_tracks[row]) session, self.db.id, row, playlist_tracks[row])
def _set_column_widths(self): def _set_column_widths(self):
# Column widths from settings # Column widths from settings
with Session() as session:
for column in range(self.columnCount()): for column in range(self.columnCount()):
# Only show column 0 in test mode # Only show column 0 in test mode
if (column == 0 and not Config.TESTMODE): if (column == 0 and not Config.TESTMODE):
self.setColumnWidth(0, 0) self.setColumnWidth(0, 0)
else: else:
name = f"playlist_col_{str(column)}_width" name = f"playlist_col_{str(column)}_width"
record = Settings.get_int(name) record = Settings.get_int(session, name)
if record.f_int is not None: if record.f_int is not None:
self.setColumnWidth(column, record.f_int) self.setColumnWidth(column, record.f_int)

View File

@ -7,7 +7,7 @@ import tempfile
from config import Config from config import Config
from log import DEBUG, INFO from log import DEBUG, INFO
from model import Tracks, session from model import Tracks, Session
from mutagen.flac import FLAC from mutagen.flac import FLAC
from pydub import AudioSegment, effects from pydub import AudioSegment, effects
from tinytag import TinyTag from tinytag import TinyTag
@ -28,15 +28,16 @@ def main():
# Run as required # Run as required
if args.update: if args.update:
INFO("Updating database") INFO("Updating database")
update_db() with Session() as session:
update_db(session)
INFO("Finished") INFO("Finished")
def add_path_to_db(path): def add_path_to_db(session, path):
"Add passed path to database along with metadata" "Add passed path to database along with metadata"
track = Tracks.get_or_create(path) track = Tracks.get_or_create(session, path)
tag = TinyTag.get(path) tag = TinyTag.get(path)
audio = get_audio_segment(path) audio = get_audio_segment(path)
@ -143,19 +144,20 @@ def fade_point(audio_segment, fade_threshold=Config.DBFS_FADE,
return int(trim_ms) return int(trim_ms)
def rescan_database(): # Current unused (1 June 2021)
# def rescan_database(session):
tracks = Tracks.get_all_tracks() #
total_tracks = len(tracks) # tracks = Tracks.get_all_tracks(session)
track_count = 0 # total_tracks = len(tracks)
for track in tracks: # track_count = 0
track_count += 1 # for track in tracks:
print(f"Track {track_count} of {total_tracks}") # track_count += 1
audio = get_audio_segment(track.path) # print(f"Track {track_count} of {total_tracks}")
track.start_gap = leading_silence(audio) # audio = get_audio_segment(track.path)
track.fade_at = fade_point(audio) # track.start_gap = leading_silence(audio)
track.silence_at = trailing_silence(audio) # track.fade_at = fade_point(audio)
session.commit() # track.silence_at = trailing_silence(audio)
# session.commit()
def trailing_silence(audio_segment, silence_threshold=-50.0, def trailing_silence(audio_segment, silence_threshold=-50.0,
@ -163,14 +165,14 @@ def trailing_silence(audio_segment, silence_threshold=-50.0,
return fade_point(audio_segment, silence_threshold, chunk_size) return fade_point(audio_segment, silence_threshold, chunk_size)
def update_db(): def update_db(session):
""" """
Repopulate database Repopulate database
""" """
# Search for tracks in only one of directory and database # Search for tracks in only one of directory and database
db_paths = set(Tracks.get_all_paths()) db_paths = set(Tracks.get_all_paths(session))
os_paths_list = [] os_paths_list = []
for root, dirs, files in os.walk(Config.ROOT): for root, dirs, files in os.walk(Config.ROOT):
@ -188,7 +190,7 @@ def update_db():
for path in list(os_paths - db_paths): for path in list(os_paths - db_paths):
# TODO # TODO
INFO(f"Adding to dataabase: {path}") INFO(f"Adding to dataabase: {path}")
add_path_to_db(path) add_path_to_db(session, path)
if __name__ == '__main__' and '__file__' in globals(): if __name__ == '__main__' and '__file__' in globals():