Compare commits

..

12 Commits

Author SHA1 Message Date
Keith Edmunds
125a44c645 Add 'this month then' note colour 2021-10-16 10:33:32 +01:00
Keith Edmunds
a72a86cfcc Don't prompt for duplicate track on a rescan
Fixes #87
2021-10-15 15:02:25 +01:00
Keith Edmunds
1a16b1022d Implement tab close buttons
Fixes #81
2021-09-29 21:29:20 +01:00
Keith Edmunds
69fb10fcd9 Make database update check cron-friendly.
Fixes #85
2021-09-29 20:55:39 +01:00
Keith Edmunds
1a4f842f1f Set last played time when playing track
Fixes #83
2021-09-26 08:47:00 +01:00
Keith Edmunds
69dd0235a0 Improve note colouring
- Make case insensitive
 - If not starts with key, it's a match

Fixes #71
2021-09-25 22:33:17 +01:00
Keith Edmunds
ab858a62fd Fix moving tracks with Wikipedia tabs open
Fixes #77
2021-09-25 22:22:34 +01:00
Keith Edmunds
01b531aabf Scroll to show moved tracks on drag and drop
Fixes #75
2021-09-24 15:10:17 +01:00
Keith Edmunds
6ccfae0ab1 Add note colouring by keyword
Fixes #71
2021-09-24 14:58:35 +01:00
Keith Edmunds
9cf9ef9a59 Add ^T shortcut to add note
Fixes #69
2021-09-24 14:43:33 +01:00
Keith Edmunds
21fe8fff83 Update track.lastplayed field
Fixes #78
2021-09-24 08:05:01 +01:00
Keith Edmunds
780b053219 Check for duplicate title on import
Fixes #80
2021-09-23 18:07:28 +01:00
7 changed files with 93 additions and 26 deletions

View File

@ -38,6 +38,13 @@ class Config(object):
MILLISECOND_SIGFIGS = 0
MYSQL_CONNECT = os.environ.get('MYSQL_CONNECT') or "mysql+mysqldb://musicmuster:musicmuster@localhost/musicmuster_dev" # noqa E501
NORMALISE_ON_IMPORT = True
NOTE_COLOURS = {
'track': "#ffff00",
'request': "#7cf000",
'wrap': "#fffacd",
'this month then': "#c256c2",
'story': "#dda0dd",
}
ROOT = os.environ.get('ROOT') or "/home/kae/music"
TESTMODE = True
TIMER_MS = 500

View File

@ -106,7 +106,7 @@ class Playdates(Base):
pd.lastplayed = datetime.now()
pd.track_id = track.id
session.add(pd)
track.update_lastplayed()
track.update_lastplayed(session, track.id)
session.commit()
@staticmethod
@ -596,8 +596,11 @@ class Tracks(Base):
return session.query(Tracks).filter(
Tracks.id == id).one()
def update_lastplayed(self):
self.lastplayed = datetime.now()
@staticmethod
def update_lastplayed(session, track_id):
track = session.query(Tracks).filter(Tracks.id == track_id).one()
track.lastplayed = datetime.now()
session.commit()
@staticmethod
def update_artist(session, track_id, artist):

View File

@ -166,6 +166,7 @@ class Window(QMainWindow, Ui_MainWindow):
def connect_signals_slots(self):
self.actionAdd_file.triggered.connect(self.add_file)
self.actionAdd_note.triggered.connect(self.insert_note)
self.action_Clear_selection.triggered.connect(self.clear_selection)
self.actionClosePlaylist.triggered.connect(self.close_playlist_tab)
self.actionExport_playlist.triggered.connect(self.export_playlist_tab)
@ -196,6 +197,7 @@ class Window(QMainWindow, Ui_MainWindow):
self.btnSongInfo.clicked.connect(self.song_info_search)
self.btnStop.clicked.connect(self.stop)
self.spnVolume.valueChanged.connect(self.change_volume)
self.tabPlaylist.tabCloseRequested.connect(self.close_tab)
self.timer.timeout.connect(self.tick)
@ -227,6 +229,15 @@ class Window(QMainWindow, Ui_MainWindow):
index = self.tabPlaylist.currentIndex()
self.tabPlaylist.removeTab(index)
def close_tab(self, index):
if self.tabPlaylist.widget(index) == self.current_track_playlist_tab:
self.statusbar.showMessage("Can't close current track playlist",
5000)
elif self.tabPlaylist.widget(index) == self.next_track_playlist_tab:
self.statusbar.showMessage("Can't close next track playlist", 5000)
else:
self.tabPlaylist.removeTab(index)
def create_note(self, session, text):
"""
Create note
@ -419,6 +430,9 @@ class Window(QMainWindow, Ui_MainWindow):
# the playlistis opened.
destination_visible_playlist_tab = None
for tab in range(self.tabPlaylist.count()):
# Non-playlist tabs won't have ids
if not hasattr(self.tabPlaylist.widget(tab), 'id'):
continue
if self.tabPlaylist.widget(tab).id == dlg.plid:
destination_visible_playlist_tab = (
self.tabPlaylist.widget(tab))
@ -511,7 +525,6 @@ class Window(QMainWindow, Ui_MainWindow):
QColor(Config.COLOUR_NEXT_TAB))
# Tell database to record it as played
self.current_track.update_lastplayed()
Playdates.add_playdate(session, self.current_track)
self.disable_play_next_controls()

View File

@ -141,6 +141,8 @@ class PlaylistTab(QTableWidget):
self.setSpan(row, self.COL_NOTE, self.NOTE_ROW_SPAN,
self.NOTE_COL_SPAN)
# Scroll to drop zone
self.scrollToItem(self.item(row, 1))
super().dropEvent(event)
DEBUG(
@ -1028,8 +1030,13 @@ class PlaylistTab(QTableWidget):
if row_time:
next_start_time = row_time
# Set colour
note_colour = Config.COLOUR_NOTES_PLAYLIST
note_text = self.item(row, self.COL_TITLE).text()
for colour_token in Config.NOTE_COLOURS.keys():
if note_text.lower().startswith(colour_token.lower()):
note_colour = Config.NOTE_COLOURS[colour_token]
self._set_row_colour(
row, QColor(Config.COLOUR_NOTES_PLAYLIST)
row, QColor(note_colour)
)
self._set_row_bold(row)
@ -1044,6 +1051,10 @@ class PlaylistTab(QTableWidget):
# Set start time
self._set_row_start_time(
row, self.current_track_start_time)
last_played_str = get_relative_date(
self.current_track_start_time)
self.item(row, self.COL_LAST_PLAYED).setText(
last_played_str)
# Calculate next_start_time
next_start_time = self._calculate_next_start_time(
session, row, self.current_track_start_time)

View File

@ -14,11 +14,13 @@ from mutagen.mp3 import MP3
from pydub import AudioSegment, effects
from tinytag import TinyTag
# Globals (I know)
messages = []
def main():
"Main loop"
INFO("Starting")
DEBUG("Starting")
# Parse command line
p = argparse.ArgumentParser()
@ -36,25 +38,25 @@ def main():
# Run as required
if args.update:
INFO("Updating database")
DEBUG("Updating database")
with Session() as session:
update_db(session)
elif args.full_update:
INFO("Full update of database")
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, verbose=True)
create_track_from_file(session, fname, interactive=True)
else:
INFO("No action specified")
INFO("Finished")
DEBUG("Finished")
def create_track_from_file(session, path, verbose=False):
def create_track_from_file(session, path, interactive=False):
"""
Create track in database from passed path, or update database entry
if path already in database.
@ -62,23 +64,35 @@ def create_track_from_file(session, path, verbose=False):
Return track.
"""
if verbose:
if interactive:
str = f"Importing {path}"
INFO(str)
INFO("-" * len(str))
track = Tracks.get_or_create(session, path)
if verbose:
INFO("Get track info...")
t = get_music_info(path)
track.title = t['title']
track.artist = t['artist']
if verbose:
INFO(f" Title: \"{track.title}\"")
INFO(f" Artist: \"{track.artist}\"")
title = t['title']
artist = t['artist']
if interactive:
INFO(f" Title: \"{title}\"")
INFO(f" Artist: \"{artist}\"")
# Check for duplicate
tracks = Tracks.search_titles(session, title)
if interactive and 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 = title
track.artist = artist
track.duration = int(round(
t['duration'], Config.MILLISECOND_SIGFIGS) * 1000)
if verbose:
if interactive:
INFO("Parse for start, fade and silence...")
audio = get_audio_segment(path)
track.start_gap = leading_silence(audio)
@ -90,7 +104,7 @@ def create_track_from_file(session, path, verbose=False):
session.commit()
if Config.NORMALISE_ON_IMPORT:
if verbose:
if interactive:
INFO("Normalise...")
# Check type
ftype = os.path.splitext(path)[1][1:]
@ -317,8 +331,7 @@ def update_db(session):
# is filename in database?
track = Tracks.get_track_from_filename(session, os.path.basename(path))
if not track:
INFO(f"songdb.update_db: Adding to database: {path}")
create_track_from_file(session, path)
messages.append(f"Track missing from database: {path}")
else:
# Check track info matches found track
t = get_music_info(path)
@ -333,7 +346,7 @@ def update_db(session):
for path in list(db_paths - os_paths):
# Manage tracks listed in database but where path is invalid
track = Tracks.get_track_from_path(session, path)
INFO(f"songdb.update_db(): remove from database: {path=} {track=}")
messages.append(f"Remove from database: {path=} {track=}")
# Remove references from Playdates
Playdates.remove_track(session, track.id)
@ -352,6 +365,12 @@ def update_db(session):
# Remove Track entry pointing to invalid path
Tracks.remove_path(session, path)
# Output messages (so if running via cron, these will get sent to
# user)
if messages:
print("Messages")
print("\n".join(messages))
def update_meta(session, track, artist=None, title=None):
"""

View File

@ -468,7 +468,7 @@ border: 1px solid rgb(85, 87, 83);</string>
<bool>false</bool>
</property>
<property name="tabsClosable">
<bool>false</bool>
<bool>true</bool>
</property>
<property name="movable">
<bool>true</bool>
@ -770,6 +770,7 @@ border: 1px solid rgb(85, 87, 83);</string>
<addaction name="separator"/>
<addaction name="actionSearch_database"/>
<addaction name="actionAdd_file"/>
<addaction name="actionAdd_note"/>
<addaction name="action_Clear_selection"/>
<addaction name="separator"/>
<addaction name="actionSelect_unplayed_tracks"/>
@ -1004,6 +1005,14 @@ border: 1px solid rgb(85, 87, 83);</string>
<string>Select unplayed tracks</string>
</property>
</action>
<action name="actionAdd_note">
<property name="text">
<string>Add note...</string>
</property>
<property name="shortcut">
<string>Ctrl+T</string>
</property>
</action>
</widget>
<customwidgets>
<customwidget>

View File

@ -208,7 +208,7 @@ class Ui_MainWindow(object):
self.gridLayout_3.addWidget(self.frame_5, 1, 0, 1, 1)
self.tabPlaylist = QtWidgets.QTabWidget(self.centralwidget)
self.tabPlaylist.setDocumentMode(False)
self.tabPlaylist.setTabsClosable(False)
self.tabPlaylist.setTabsClosable(True)
self.tabPlaylist.setMovable(True)
self.tabPlaylist.setObjectName("tabPlaylist")
self.gridLayout_3.addWidget(self.tabPlaylist, 2, 0, 1, 1)
@ -438,6 +438,8 @@ class Ui_MainWindow(object):
self.actionSelect_played_tracks.setObjectName("actionSelect_played_tracks")
self.actionSelect_unplayed_tracks = QtWidgets.QAction(MainWindow)
self.actionSelect_unplayed_tracks.setObjectName("actionSelect_unplayed_tracks")
self.actionAdd_note = QtWidgets.QAction(MainWindow)
self.actionAdd_note.setObjectName("actionAdd_note")
self.menuFile.addAction(self.actionE_xit)
self.menuPlaylist.addAction(self.actionNewPlaylist)
self.menuPlaylist.addAction(self.actionOpenPlaylist)
@ -447,6 +449,7 @@ class Ui_MainWindow(object):
self.menuPlaylist.addSeparator()
self.menuPlaylist.addAction(self.actionSearch_database)
self.menuPlaylist.addAction(self.actionAdd_file)
self.menuPlaylist.addAction(self.actionAdd_note)
self.menuPlaylist.addAction(self.action_Clear_selection)
self.menuPlaylist.addSeparator()
self.menuPlaylist.addAction(self.actionSelect_unplayed_tracks)
@ -545,5 +548,7 @@ class Ui_MainWindow(object):
self.actionSelect_previous_track.setShortcut(_translate("MainWindow", "K"))
self.actionSelect_played_tracks.setText(_translate("MainWindow", "Select played tracks"))
self.actionSelect_unplayed_tracks.setText(_translate("MainWindow", "Select unplayed tracks"))
self.actionAdd_note.setText(_translate("MainWindow", "Add note..."))
self.actionAdd_note.setShortcut(_translate("MainWindow", "Ctrl+T"))
from musicmuster import ElideLabel
import icons_rc