Greatly improve database update
This commit is contained in:
parent
28396d136f
commit
a027cbe776
64
app/model.py
64
app/model.py
@ -14,6 +14,7 @@ from sqlalchemy import (
|
||||
String,
|
||||
func
|
||||
)
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from sqlalchemy.orm.exc import MultipleResultsFound, NoResultFound
|
||||
from sqlalchemy.orm import relationship, sessionmaker
|
||||
|
||||
@ -106,6 +107,14 @@ class Playdates(Base):
|
||||
track.update_lastplayed()
|
||||
session.commit()
|
||||
|
||||
@staticmethod
|
||||
def last_played(session, track):
|
||||
"Return datetime track last played or None"
|
||||
|
||||
return session.query(Playdates).filter(
|
||||
(Playdates.track_id == track.id)
|
||||
).order_by(Playdates.lastplayed.desc()).first()
|
||||
|
||||
|
||||
class Playlists(Base):
|
||||
"""
|
||||
@ -279,6 +288,20 @@ class PlaylistTracks(Base):
|
||||
session.add(plt)
|
||||
session.commit()
|
||||
|
||||
@staticmethod
|
||||
def get_playlists_containing_track_id(session, track_id):
|
||||
|
||||
playlists = []
|
||||
playlist_ids = session.query(PlaylistTracks.playlist_id).filter(
|
||||
PlaylistTracks.track_id == track_id).all()
|
||||
for p in playlist_ids:
|
||||
playlist = session.query(Playlists).filter(
|
||||
Playlists.id == p[0]).first()
|
||||
if playlist:
|
||||
playlists.append(playlist)
|
||||
|
||||
return playlists
|
||||
|
||||
@staticmethod
|
||||
def move_track(session, from_playlist_id, row, to_playlist_id):
|
||||
DEBUG(
|
||||
@ -458,6 +481,44 @@ class Tracks(Base):
|
||||
ERROR(f"get_track({id}): not found")
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def get_track_from_filename(session, filename):
|
||||
"""
|
||||
Return track if one and only one track in database has passed
|
||||
filename (ie, basename of path). Return None if zero or more
|
||||
than one track matches.
|
||||
"""
|
||||
|
||||
DEBUG(f"Tracks.get_track_from_filename({filename=})")
|
||||
try:
|
||||
track = session.query(Tracks).filter(Tracks.path.ilike(
|
||||
# TODO: filename separator is hardcoded here
|
||||
f'%/{filename}')).one()
|
||||
return track
|
||||
except (NoResultFound, MultipleResultsFound):
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def get_track_from_path(session, path):
|
||||
"""
|
||||
Return track with passee path, or None.
|
||||
"""
|
||||
|
||||
DEBUG(f"Tracks.get_track_from_path({path=})")
|
||||
|
||||
return session.query(Tracks).filter(Tracks.path == path).first()
|
||||
|
||||
@staticmethod
|
||||
def remove_path(session, path):
|
||||
"Remove track with passed path from database"
|
||||
|
||||
DEBUG(f"Tracks.remove_path({path=})")
|
||||
|
||||
try:
|
||||
session.query(Tracks).filter(Tracks.path == path).delete()
|
||||
except IntegrityError as exception:
|
||||
ERROR(f"Can't remove track with {path=} ({exception=})")
|
||||
|
||||
@staticmethod
|
||||
def search_titles(session, text):
|
||||
return (
|
||||
@ -473,3 +534,6 @@ class Tracks(Base):
|
||||
|
||||
def update_lastplayed(self):
|
||||
self.lastplayed = datetime.now()
|
||||
|
||||
def update_path(self, newpath):
|
||||
self.path = newpath
|
||||
|
||||
@ -7,7 +7,7 @@ import tempfile
|
||||
|
||||
from config import Config
|
||||
from log import DEBUG, INFO
|
||||
from model import Tracks, Session
|
||||
from model import Tracks, Playdates, PlaylistTracks, Session
|
||||
from mutagen.flac import FLAC
|
||||
from pydub import AudioSegment, effects
|
||||
from tinytag import TinyTag
|
||||
@ -22,7 +22,10 @@ def main():
|
||||
p = argparse.ArgumentParser()
|
||||
p.add_argument('-u', '--update',
|
||||
action="store_true", dest="update",
|
||||
default=True, help="Update database")
|
||||
default=False, help="Update database")
|
||||
p.add_argument('-f', '--full-update',
|
||||
action="store_true", dest="full_update",
|
||||
default=False, help="Update database")
|
||||
args = p.parse_args()
|
||||
|
||||
# Run as required
|
||||
@ -30,25 +33,32 @@ def main():
|
||||
INFO("Updating database")
|
||||
with Session() as session:
|
||||
update_db(session)
|
||||
elif args.full_update:
|
||||
INFO("Full update of database")
|
||||
with Session() as session:
|
||||
full_update_db(session)
|
||||
else:
|
||||
INFO("No action specified")
|
||||
|
||||
INFO("Finished")
|
||||
|
||||
|
||||
def create_track_from_file(session, path):
|
||||
"""
|
||||
Create track in database from passed path.
|
||||
Create track in database from passed path, or update database entry
|
||||
if path already in database.
|
||||
|
||||
Return track.
|
||||
"""
|
||||
|
||||
track = Tracks.get_or_create(session, path)
|
||||
tag = TinyTag.get(path)
|
||||
audio = get_audio_segment(path)
|
||||
t = get_music_info(path)
|
||||
track.title = t['title']
|
||||
track.artist = t['artist']
|
||||
track.duration = int(round(
|
||||
t['duration'], Config.MILLISECOND_SIGFIGS) * 1000)
|
||||
|
||||
track.title = tag.title
|
||||
track.artist = tag.artist
|
||||
track.duration = int(round(tag.duration,
|
||||
Config.MILLISECOND_SIGFIGS) * 1000)
|
||||
audio = get_audio_segment(path)
|
||||
track.start_gap = leading_silence(audio)
|
||||
track.fade_at = round(fade_point(audio) / 1000,
|
||||
Config.MILLISECOND_SIGFIGS) * 1000
|
||||
@ -92,6 +102,12 @@ def create_track_from_file(session, path):
|
||||
return track
|
||||
|
||||
|
||||
def full_update_db():
|
||||
"Rescan all entries in database"
|
||||
|
||||
pass
|
||||
|
||||
|
||||
def get_audio_segment(path):
|
||||
try:
|
||||
if path.endswith('.mp3'):
|
||||
@ -102,6 +118,21 @@ def get_audio_segment(path):
|
||||
return None
|
||||
|
||||
|
||||
def get_music_info(path):
|
||||
"""
|
||||
Return a dictionary of title, artist, duration-in-milliseconds and path.
|
||||
"""
|
||||
|
||||
tag = TinyTag.get(path)
|
||||
|
||||
return dict(
|
||||
title=tag.title,
|
||||
artist=tag.artist,
|
||||
duration=tag.duration,
|
||||
path=path
|
||||
)
|
||||
|
||||
|
||||
def leading_silence(audio_segment, silence_threshold=Config.DBFS_SILENCE,
|
||||
chunk_size=Config.AUDIO_SEGMENT_CHUNK_SIZE):
|
||||
"""
|
||||
@ -187,14 +218,47 @@ def update_db(session):
|
||||
os_paths_list.append(path)
|
||||
os_paths = set(os_paths_list)
|
||||
|
||||
for path in list(db_paths - os_paths):
|
||||
# TODO
|
||||
INFO(f"To remove from database: {path}")
|
||||
# If a track is moved, only the path will have changed.
|
||||
# For any files we have found whose paths are not in the database,
|
||||
# check to see whether the filename (basename) is present in the
|
||||
# database:
|
||||
|
||||
for path in list(os_paths - db_paths):
|
||||
# TODO
|
||||
INFO(f"Adding to dataabase: {path}")
|
||||
create_track_from_file(session, path)
|
||||
DEBUG(f"songdb.update_db: {path=} not in database")
|
||||
# is filename in database?
|
||||
track = Tracks.get_track_from_filename(session, os.path.basename(path))
|
||||
if not track:
|
||||
DEBUG(f"songdb.update_db: Adding to database: {path}")
|
||||
create_track_from_file(session, path)
|
||||
else:
|
||||
# Check track info matches found track
|
||||
t = get_music_info(path)
|
||||
if t['artist'] == track.artist and t['title'] == track.title:
|
||||
track.update_path(path)
|
||||
else:
|
||||
create_track_from_file(session, path)
|
||||
|
||||
# Refresh database paths
|
||||
db_paths = set(Tracks.get_all_paths(session))
|
||||
# Remote any tracks from database whose paths don't exist
|
||||
for path in list(db_paths - os_paths):
|
||||
track = Tracks.get_track_from_path(session, path)
|
||||
DEBUG(f"songdb.update_db(): remove from database: {path=} {track=}")
|
||||
played = Playdates.last_played(session, track)
|
||||
playlists = PlaylistTracks.get_playlists_containing_track_id(
|
||||
session, track.id)
|
||||
if played:
|
||||
INFO(
|
||||
f"songdb.update_db: Can't remove {track.id=} ({track.path=}) "
|
||||
f"as it's in playdates.id={played.id}"
|
||||
)
|
||||
elif playlists:
|
||||
INFO(
|
||||
f"songdb.update_db: Can't remove {track.id=} ({track.path=} "
|
||||
f"as it's in playlists {[p.name for p in playlists]}"
|
||||
)
|
||||
else:
|
||||
Tracks.remove_path(session, path)
|
||||
|
||||
|
||||
if __name__ == '__main__' and '__file__' in globals():
|
||||
|
||||
Loading…
Reference in New Issue
Block a user