Compare commits
No commits in common. "42092d3d392c34350260729d9241162eaaf92b8b" and "e2af6dd7ac18e4d83dd30493e76d6aff857da185" have entirely different histories.
42092d3d39
...
e2af6dd7ac
@ -54,7 +54,6 @@ class Config(object):
|
||||
NORMALISE_ON_IMPORT = True
|
||||
NOTE_TIME_FORMAT = "%H:%M:%S"
|
||||
ROOT = os.environ.get('ROOT') or "/home/kae/music"
|
||||
IMPORT_DESTINATION = os.path.join(ROOT, "Singles")
|
||||
SCROLL_TOP_MARGIN = 3
|
||||
TESTMODE = True
|
||||
TOD_TIME_FORMAT = "%H:%M:%S"
|
||||
|
||||
@ -63,13 +63,12 @@ def get_tags(path: str) -> Dict[str, Union[str, int]]:
|
||||
|
||||
tag: TinyTag = TinyTag.get(path)
|
||||
|
||||
d = dict(
|
||||
return dict(
|
||||
title=tag.title,
|
||||
artist=tag.artist,
|
||||
duration=int(round(tag.duration, Config.MILLISECOND_SIGFIGS) * 1000),
|
||||
path=path
|
||||
)
|
||||
return d
|
||||
|
||||
|
||||
def get_relative_date(past_date: datetime, reference_date: datetime = None) \
|
||||
|
||||
@ -341,14 +341,13 @@ class Playlists(Base):
|
||||
def remove_track(self, session: Session, row: int) -> None:
|
||||
DEBUG(f"Playlist.remove_track({self.id=}, {row=})")
|
||||
|
||||
# Refresh self first (this is necessary when calling remove_track
|
||||
# multiple times before session.commit())
|
||||
session.refresh(self)
|
||||
# Get tracks collection for this playlist
|
||||
tracks_collections = self.tracks
|
||||
# Tracks are a dictionary of tracks keyed on row
|
||||
# number. Remove the relevant row.
|
||||
del self.tracks[row]
|
||||
del tracks_collections[row]
|
||||
# Save the new tracks collection
|
||||
self.tracks = tracks_collections
|
||||
session.flush()
|
||||
|
||||
|
||||
|
||||
@ -4,7 +4,6 @@ import argparse
|
||||
import os.path
|
||||
import psutil
|
||||
import sys
|
||||
import threading
|
||||
import urllib.parse
|
||||
import webbrowser
|
||||
|
||||
@ -25,7 +24,6 @@ from PyQt5.QtWidgets import (
|
||||
QLineEdit,
|
||||
QListWidgetItem,
|
||||
QMainWindow,
|
||||
QMessageBox,
|
||||
)
|
||||
|
||||
import dbconfig
|
||||
@ -422,56 +420,18 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
dlg = QFileDialog()
|
||||
dlg.setFileMode(QFileDialog.ExistingFiles)
|
||||
dlg.setViewMode(QFileDialog.Detail)
|
||||
dlg.setDirectory(Config.IMPORT_DESTINATION)
|
||||
# TODO: remove hardcoded directory
|
||||
dlg.setDirectory(os.path.join(Config.ROOT, "Singles"))
|
||||
dlg.setNameFilter("Music files (*.flac *.mp3)")
|
||||
|
||||
if dlg.exec_():
|
||||
with Session() as session:
|
||||
txt: str = ""
|
||||
new_tracks = []
|
||||
for fname in dlg.selectedFiles():
|
||||
tags = helpers.get_tags(fname)
|
||||
new_tracks.append((fname, tags))
|
||||
title = tags['title']
|
||||
artist = tags['artist']
|
||||
possible_matches = Tracks.search_titles(session, title)
|
||||
if possible_matches:
|
||||
txt += 'Similar to new track '
|
||||
txt += f'"{title}" by "{artist} ({fname})":\n\n'
|
||||
for track in possible_matches:
|
||||
txt += f' "{track.title}" by {track.artist}'
|
||||
txt += f' ({track.path})\n'
|
||||
txt += "\n"
|
||||
# Check whether to proceed if there were potential matches
|
||||
if txt:
|
||||
txt += "Proceed with import?"
|
||||
result = QMessageBox.question(self,
|
||||
"Possible duplicates",
|
||||
txt,
|
||||
QMessageBox.Ok,
|
||||
QMessageBox.Cancel
|
||||
)
|
||||
if result == QMessageBox.Cancel:
|
||||
return
|
||||
|
||||
# Import in separate thread
|
||||
thread = threading.Thread(target=self._import_tracks,
|
||||
args=(new_tracks,))
|
||||
thread.start()
|
||||
|
||||
def _import_tracks(self, tracks: list):
|
||||
"""
|
||||
Import passed files. Don't use parent session as that may be invalid
|
||||
by the time we need it.
|
||||
"""
|
||||
|
||||
with Session() as session:
|
||||
for (fname, tags) in tracks:
|
||||
track = create_track_from_file(session, fname, tags=tags)
|
||||
# Add to playlist on screen
|
||||
# If we don't specify "repaint=False", playlist will
|
||||
# also be saved to database
|
||||
self.visible_playlist_tab().insert_track(session, track)
|
||||
track = create_track_from_file(session, fname)
|
||||
# Add to playlist on screen
|
||||
# If we don't specify "repaint=False", playlist will
|
||||
# also be saved to database
|
||||
self.visible_playlist_tab().insert_track(session, track)
|
||||
|
||||
def load_last_playlists(self):
|
||||
"""Load the playlists that we loaded at end of last session"""
|
||||
@ -1019,8 +979,7 @@ class DbDialog(QDialog):
|
||||
t = QListWidgetItem()
|
||||
t.setText(
|
||||
f"{track.title} - {track.artist} "
|
||||
f"[{helpers.ms_to_mmss(track.duration)}] "
|
||||
f"({helpers.get_relative_date(track.lastplayed)})"
|
||||
f"[{helpers.ms_to_mmss(track.duration)}]"
|
||||
)
|
||||
t.setData(Qt.UserRole, track)
|
||||
self.ui.matchList.addItem(t)
|
||||
|
||||
@ -1033,7 +1033,7 @@ class PlaylistTab(QTableWidget):
|
||||
|
||||
DEBUG("playlist._delete_rows()")
|
||||
|
||||
selected_rows: List[int] = sorted(
|
||||
rows: List[int] = sorted(
|
||||
set(item.row() for item in self.selectedItems())
|
||||
)
|
||||
rows_to_delete: List[int] = []
|
||||
@ -1042,22 +1042,17 @@ class PlaylistTab(QTableWidget):
|
||||
row_object: Union[Tracks, Notes]
|
||||
|
||||
with Session() as session:
|
||||
for row in selected_rows:
|
||||
for row in rows:
|
||||
title = self.item(row, self.COL_TITLE).text()
|
||||
msg = QMessageBox(self)
|
||||
msg.setIcon(QMessageBox.Warning)
|
||||
msg.setText(f"Delete '{title}'?")
|
||||
msg.setStandardButtons(
|
||||
QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel
|
||||
)
|
||||
msg.setDefaultButton(QMessageBox.No)
|
||||
msg.setStandardButtons(QMessageBox.Yes | QMessageBox.Cancel)
|
||||
msg.setDefaultButton(QMessageBox.Cancel)
|
||||
msg.setWindowTitle("Delete row")
|
||||
# Store list of rows to delete
|
||||
response = msg.exec()
|
||||
if response == QMessageBox.Yes:
|
||||
if msg.exec() == QMessageBox.Yes:
|
||||
rows_to_delete.append(row)
|
||||
elif response == QMessageBox.Cancel:
|
||||
return
|
||||
|
||||
# delete in reverse row order so row numbers don't
|
||||
# change
|
||||
|
||||
@ -38,6 +38,7 @@ def main():
|
||||
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")
|
||||
args = p.parse_args()
|
||||
|
||||
# Run as required
|
||||
@ -49,13 +50,18 @@ def main():
|
||||
DEBUG("Full update of database")
|
||||
with Session() as session:
|
||||
full_update_db(session)
|
||||
elif args.fname:
|
||||
fname = os.path.realpath(args.fname)
|
||||
with Session() as session:
|
||||
create_track_from_file(session, fname, interactive=True)
|
||||
|
||||
else:
|
||||
INFO("No action specified")
|
||||
|
||||
DEBUG("Finished")
|
||||
|
||||
|
||||
def create_track_from_file(session, path, normalise=None, tags=None):
|
||||
def create_track_from_file(session, path, normalise=None, interactive=False):
|
||||
"""
|
||||
Create track in database from passed path, or update database entry
|
||||
if path already in database.
|
||||
@ -63,14 +69,34 @@ def create_track_from_file(session, path, normalise=None, tags=None):
|
||||
Return track.
|
||||
"""
|
||||
|
||||
if not tags:
|
||||
t = get_tags(path)
|
||||
else:
|
||||
t = tags
|
||||
|
||||
if interactive:
|
||||
msg = f"Importing {path}"
|
||||
INFO(msg)
|
||||
INFO("-" * len(msg))
|
||||
INFO("Get track info...")
|
||||
t = get_tags(path)
|
||||
title = t['title']
|
||||
artist = t['artist']
|
||||
if interactive:
|
||||
INFO(f" Title: \"{title}\"")
|
||||
INFO(f" Artist: \"{artist}\"")
|
||||
# Check for duplicate
|
||||
if interactive:
|
||||
tracks = Tracks.search_titles(session, title)
|
||||
if tracks:
|
||||
print("Found the following possible matches:")
|
||||
for track in tracks:
|
||||
print(f'"{track.title}" by {track.artist}')
|
||||
response = input("Continue [c] or abort [a]?")
|
||||
if not response:
|
||||
return
|
||||
if response[0].lower() not in ['c', 'y']:
|
||||
return
|
||||
track = Tracks.get_or_create(session, path)
|
||||
track.title = t['title']
|
||||
track.artist = t['artist']
|
||||
track.title = title
|
||||
track.artist = artist
|
||||
if interactive:
|
||||
INFO("Parse for start, fade and silence...")
|
||||
audio = get_audio_segment(path)
|
||||
track.duration = len(audio)
|
||||
track.start_gap = leading_silence(audio)
|
||||
@ -82,6 +108,8 @@ def create_track_from_file(session, path, normalise=None, tags=None):
|
||||
session.commit()
|
||||
|
||||
if normalise or normalise is None and Config.NORMALISE_ON_IMPORT:
|
||||
if interactive:
|
||||
INFO("Normalise...")
|
||||
# Check type
|
||||
ftype = os.path.splitext(path)[1][1:]
|
||||
if ftype not in ['mp3', 'flac']:
|
||||
@ -224,7 +252,7 @@ def update_db(session):
|
||||
|
||||
for path in list(os_paths - db_paths):
|
||||
DEBUG(f"utilities.update_db: {path=} not in database")
|
||||
# is filename in database with a different path?
|
||||
# is filename in database?
|
||||
track = Tracks.get_by_filename(session, os.path.basename(path))
|
||||
if not track:
|
||||
messages.append(f"{path} missing from database: {path}")
|
||||
|
||||
Loading…
Reference in New Issue
Block a user