Compare commits

..

No commits in common. "61311f67fef3bf219c790b96c793c6adf11b2808" and "92bdf216ca620d16b27f2decb6dbe8b852f2fb75" have entirely different histories.

4 changed files with 272 additions and 217 deletions

View File

@ -545,12 +545,18 @@ class Tracks(Base):
session.add(self) session.add(self)
session.commit() session.commit()
#
@staticmethod # @staticmethod
def get_all_paths(session) -> List[str]: # def get_all_paths(session) -> List[str]:
"""Return a list of paths of all tracks""" # """Return a list of paths of all tracks"""
#
return session.execute(select(Tracks.path)).scalars().all() # return [a[0] for a in session.query(Tracks.path).all()]
#
# @classmethod
# def get_all_tracks(cls, session: Session) -> List["Tracks"]:
# """Return a list of all tracks"""
#
# return session.query(cls).all()
@classmethod @classmethod
def get_or_create(cls, session: Session, path: str) -> "Tracks": def get_or_create(cls, session: Session, path: str) -> "Tracks":
@ -566,18 +572,48 @@ class Tracks(Base):
return track return track
@classmethod # @classmethod
def get_by_path(cls, session: Session, path: str) -> "Tracks": # def get_by_filename(cls, session: Session, filename: str) \
""" # -> Optional["Tracks"]:
Return track with passed path, or None. # """
""" # Return track if one and only one track in database has passed
# filename (ie, basename of path). Return None if zero or more
return ( # than one track matches.
session.execute( # """
select(Tracks) #
.where(Tracks.path == path) # log.debug(f"Tracks.get_track_from_filename({filename=})")
).scalar_one() # try:
) # track = session.query(Tracks).filter(Tracks.path.ilike(
# f'%{os.path.sep}{filename}')).one()
# return track
# except (NoResultFound, MultipleResultsFound):
# return None
#
# @classmethod
# def get_by_path(cls, session: Session, path: str) ->
# List["Tracks"]:
# """
# Return track with passee path, or None.
# """
#
# log.debug(f"Tracks.get_track_from_path({path=})")
#
# return session.query(Tracks).filter(Tracks.path ==
# path).first()
#
# @classmethod
# def get_by_id(cls, session: Session, track_id: int) ->
# Optional["Tracks"]:
# """Return track or None"""
#
# try:
# log.debug(f"Tracks.get_track(track_id={track_id})")
# track = session.query(Tracks).filter(Tracks.id ==
# track_id).one()
# return track
# except NoResultFound:
# log.error(f"get_track({track_id}): not found")
# return None
def rescan(self, session: Session) -> None: def rescan(self, session: Session) -> None:
""" """

View File

@ -1,7 +1,7 @@
#!/usr/bin/env python #!/usr/bin/env python
from log import log from log import log
import argparse # import argparse
import sys import sys
import threading import threading
@ -41,7 +41,7 @@ from ui.dlg_SelectPlaylist_ui import Ui_dlgSelectPlaylist # type: ignore
from ui.downloadcsv_ui import Ui_DateSelect # type: ignore from ui.downloadcsv_ui import Ui_DateSelect # type: ignore
from config import Config from config import Config
from ui.main_window_ui import Ui_MainWindow # type: ignore from ui.main_window_ui import Ui_MainWindow # type: ignore
from utilities import create_track_from_file, check_db from utilities import create_track_from_file # , update_db
class TrackData: class TrackData:
@ -1096,35 +1096,30 @@ class SelectPlaylistDialog(QDialog):
if __name__ == "__main__": if __name__ == "__main__":
""" # p = argparse.ArgumentParser()
If command line arguments given, carry out requested function and # # Only allow at most one option to be specified
exit. Otherwise run full application. # group = p.add_mutually_exclusive_group()
""" # group.add_argument('-u', '--update',
# action="store_true", dest="update",
p = argparse.ArgumentParser()
# Only allow at most one option to be specified
group = p.add_mutually_exclusive_group()
group.add_argument('-c', '--check-database',
action="store_true", dest="check_db",
default=False, help="Check and report on database")
# group.add_argument('-f', '--full-update',
# action="store_true", dest="full_update",
# default=False, help="Update database") # default=False, help="Update database")
# group.add_argument('-i', '--import', dest="fname", help="Input # # group.add_argument('-f', '--full-update',
# # action="store_true", dest="full_update",
# # default=False, help="Update database")
# # group.add_argument('-i', '--import', dest="fname", help="Input
# file") # file")
args = p.parse_args() # args = p.parse_args()
#
# Run as required # # Run as required
if args.check_db: # if args.update:
log.debug("Updating database") # log.debug("Updating database")
with Session() as session:
check_db(session)
# elif args.full_update:
# log.debug("Full update of database")
# with Session() as session: # with Session() as session:
# full_update_db(session) # update_db(session)
else: # # elif args.full_update:
# Normal run # # log.debug("Full update of database")
# # with Session() as session:
# # full_update_db(session)
# else:
# # Normal run
try: try:
Base.metadata.create_all(engine) Base.metadata.create_all(engine)
app = QApplication(sys.argv) app = QApplication(sys.argv)

View File

@ -151,8 +151,6 @@ class PlaylistTab(QTableWidget):
self.selecting_in_progress = False self.selecting_in_progress = False
# Connect signals # Connect signals
self.horizontalHeader().sectionResized.connect(self._column_resize) self.horizontalHeader().sectionResized.connect(self._column_resize)
# self.horizontalHeader().sectionClicked.connect(self._header_click)
# self.setSortingEnabled(True)
# Now load our tracks and notes # Now load our tracks and notes
self.populate(session, self.playlist_id) self.populate(session, self.playlist_id)
@ -625,7 +623,7 @@ class PlaylistTab(QTableWidget):
- 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 next track as required - Scroll to put current track as required
- Set next track - Set next track
- Update display - Update display
""" """
@ -642,6 +640,14 @@ class PlaylistTab(QTableWidget):
# Mark current row as played # Mark current row as played
self._set_played_row(session, current_row) self._set_played_row(session, current_row)
# Scroll to put current track Config.SCROLL_TOP_MARGIN from the
# top. Rows number from zero, so set (current_row -
# Config.SCROLL_TOP_MARGIN + 1) row to be top row
top_row = max(0, current_row - Config.SCROLL_TOP_MARGIN + 1)
scroll_item = self.item(top_row, 0)
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(session, search_from)
@ -1068,16 +1074,6 @@ class PlaylistTab(QTableWidget):
section_start_plr, section_start_plr,
self._get_section_timing_string(section_time, no_end=True) self._get_section_timing_string(section_time, no_end=True)
) )
# Scroll to put next track Config.SCROLL_TOP_MARGIN from the
# top. Rows number from zero, so set (current_row -
# Config.SCROLL_TOP_MARGIN + 1) row to be top row
if next_row is not None:
top_row = max(0, next_row - Config.SCROLL_TOP_MARGIN + 1)
scroll_item = self.item(top_row, 0)
self.scrollToItem(scroll_item, QAbstractItemView.PositionAtTop)
# #
# # ########## Internally called functions ########## # # ########## Internally called functions ##########
@ -1344,11 +1340,6 @@ class PlaylistTab(QTableWidget):
return self._meta_search(RowMeta.UNREADABLE, one=False) return self._meta_search(RowMeta.UNREADABLE, one=False)
# def _header_click(self, index: int) -> None:
# """Handle playlist header click"""
# print(f"_header_click({index=})")
def _info_row(self, track_id: int) -> None: def _info_row(self, track_id: int) -> None:
"""Display popup with info re row""" """Display popup with info re row"""

View File

@ -1,5 +1,6 @@
# #!/usr/bin/env python # #!/usr/bin/env python
# #
# import argparse
import os import os
import shutil import shutil
import tempfile import tempfile
@ -12,11 +13,43 @@ from helpers import (
leading_silence, leading_silence,
trailing_silence, trailing_silence,
) )
from log import log # from log import log.debug, log.info
# from models import Notes, Playdates, Session, Tracks
from models import Tracks from models import Tracks
from mutagen.flac import FLAC # type: ignore from mutagen.flac import FLAC
from mutagen.mp3 import MP3 # type: ignore from mutagen.mp3 import MP3
from pydub import effects from pydub import effects
#
#
# def main():
# """Main loop"""
#
# log.debug("Starting")
#
# p = argparse.ArgumentParser()
# # Only allow one option to be specified
# group = p.add_mutually_exclusive_group()
# group.add_argument('-u', '--update',
# action="store_true", dest="update",
# default=False, help="Update database")
# group.add_argument('-f', '--full-update',
# action="store_true", dest="full_update",
# default=False, help="Update database")
# args = p.parse_args()
#
# # Run as required
# if args.update:
# log.debug("Updating database")
# with Session() as session:
# update_db(session)
# elif args.full_update:
# log.debug("Full update of database")
# with Session() as session:
# full_update_db(session)
# else:
# log.info("No action specified")
#
# log.debug("Finished")
def create_track_from_file(session, path, normalise=None, tags=None): def create_track_from_file(session, path, normalise=None, tags=None):
@ -94,7 +127,8 @@ def create_track_from_file(session, path, normalise=None, tags=None):
os.remove(temp_path) os.remove(temp_path)
return track return track
#
#
# def full_update_db(session): # def full_update_db(session):
# """Rescan all entries in database""" # """Rescan all entries in database"""
# #
@ -164,74 +198,70 @@ def create_track_from_file(session, path, normalise=None, tags=None):
# silence_at) # silence_at)
# track.silence_at = silence_at # track.silence_at = silence_at
# session.commit() # session.commit()
#
#
def check_db(session): # def update_db(session):
""" # """
Database consistency check. # Repopulate database
# """
A report is generated if issues are found, but there are no automatic #
corrections made. # # Search for tracks that are in the music directory but not the datebase
# # Check all paths in database exist
Search for tracks that are in the music directory but not the datebase # # If issues found, write to stdout but do not try to resolve them
Check all paths in database exist #
""" # db_paths = set(Tracks.get_all_paths(session))
#
db_paths = set(Tracks.get_all_paths(session)) # os_paths_list = []
# for root, dirs, files in os.walk(Config.ROOT):
os_paths_list = [] # for f in files:
for root, dirs, files in os.walk(Config.ROOT): # path = os.path.join(root, f)
for f in files: # ext = os.path.splitext(f)[1]
path = os.path.join(root, f) # if ext in [".flac", ".mp3"]:
ext = os.path.splitext(f)[1] # os_paths_list.append(path)
if ext in [".flac", ".mp3"]: # os_paths = set(os_paths_list)
os_paths_list.append(path) #
os_paths = set(os_paths_list) # # Find any files in music directory that are not in database
# files_not_in_db = list(os_paths - db_paths)
# Find any files in music directory that are not in database #
files_not_in_db = list(os_paths - db_paths) # # Find paths in database missing in music directory
# paths_not_found = []
# Find paths in database missing in music directory # missing_file_count = 0
paths_not_found = [] # more_files_to_report = False
missing_file_count = 0 # for path in list(db_paths - os_paths):
more_files_to_report = False # if missing_file_count >= Config.MAX_MISSING_FILES_TO_REPORT:
for path in list(db_paths - os_paths): # more_files_to_report = True
if missing_file_count >= Config.MAX_MISSING_FILES_TO_REPORT: # break
more_files_to_report = True #
break # missing_file_count += 1
#
missing_file_count += 1 # track = Tracks.get_by_path(session, path)
# if not track:
track = Tracks.get_by_path(session, path) # log.error(f"update_db: {path} not found in db")
if not track: # continue
# This shouldn't happen as we're looking for paths in #
# database that aren't in filesystem, but just in case... # paths_not_found.append(track)
log.error(f"update_db: {path} not found in db") #
continue # # Output messages (so if running via cron, these will get sent to
# # user)
paths_not_found.append(track) # if files_not_in_db:
# print("Files in music directory but not in database")
# Output messages (so if running via cron, these will get sent to # print("--------------------------------------------")
# user) # print("\n".join(files_not_in_db))
if files_not_in_db: # print("\n")
print("Files in music directory but not in database") # if paths_not_found:
print("--------------------------------------------") # print("Invalid paths in database")
print("\n".join(files_not_in_db)) # print("-------------------------")
print("\n") # for t in paths_not_found:
if paths_not_found: # print(f"""
print("Invalid paths in database") # Track ID: {t.id}
print("-------------------------") # Path: {t.path}
for t in paths_not_found: # Title: {t.title}
print(f""" # Artist: {t.artist}
Track ID: {t.id} # """)
Path: {t.path} # if more_files_to_report:
Title: {t.title} # print("There were more paths than listed that were not found")
Artist: {t.artist} #
""") #
if more_files_to_report:
print("There were more paths than listed that were not found")
# # Spike # # Spike
# # # #
# # # Manage tracks listed in database but where path is invalid # # # Manage tracks listed in database but where path is invalid
@ -258,3 +288,6 @@ def check_db(session):
# # # #
# # # Remove Track entry pointing to invalid path # # # Remove Track entry pointing to invalid path
# # Tracks.remove_by_path(session, path) # # Tracks.remove_by_path(session, path)
#
# if __name__ == '__main__' and '__file__' in globals():
# main()