Compare commits
12 Commits
2fbf829eed
...
125a44c645
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
125a44c645 | ||
|
|
a72a86cfcc | ||
|
|
1a16b1022d | ||
|
|
69fb10fcd9 | ||
|
|
1a4f842f1f | ||
|
|
69dd0235a0 | ||
|
|
ab858a62fd | ||
|
|
01b531aabf | ||
|
|
6ccfae0ab1 | ||
|
|
9cf9ef9a59 | ||
|
|
21fe8fff83 | ||
|
|
780b053219 |
@ -38,6 +38,13 @@ class Config(object):
|
|||||||
MILLISECOND_SIGFIGS = 0
|
MILLISECOND_SIGFIGS = 0
|
||||||
MYSQL_CONNECT = os.environ.get('MYSQL_CONNECT') or "mysql+mysqldb://musicmuster:musicmuster@localhost/musicmuster_dev" # noqa E501
|
MYSQL_CONNECT = os.environ.get('MYSQL_CONNECT') or "mysql+mysqldb://musicmuster:musicmuster@localhost/musicmuster_dev" # noqa E501
|
||||||
NORMALISE_ON_IMPORT = True
|
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"
|
ROOT = os.environ.get('ROOT') or "/home/kae/music"
|
||||||
TESTMODE = True
|
TESTMODE = True
|
||||||
TIMER_MS = 500
|
TIMER_MS = 500
|
||||||
|
|||||||
@ -106,7 +106,7 @@ class Playdates(Base):
|
|||||||
pd.lastplayed = datetime.now()
|
pd.lastplayed = datetime.now()
|
||||||
pd.track_id = track.id
|
pd.track_id = track.id
|
||||||
session.add(pd)
|
session.add(pd)
|
||||||
track.update_lastplayed()
|
track.update_lastplayed(session, track.id)
|
||||||
session.commit()
|
session.commit()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -596,8 +596,11 @@ class Tracks(Base):
|
|||||||
return session.query(Tracks).filter(
|
return session.query(Tracks).filter(
|
||||||
Tracks.id == id).one()
|
Tracks.id == id).one()
|
||||||
|
|
||||||
def update_lastplayed(self):
|
@staticmethod
|
||||||
self.lastplayed = datetime.now()
|
def update_lastplayed(session, track_id):
|
||||||
|
track = session.query(Tracks).filter(Tracks.id == track_id).one()
|
||||||
|
track.lastplayed = datetime.now()
|
||||||
|
session.commit()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def update_artist(session, track_id, artist):
|
def update_artist(session, track_id, artist):
|
||||||
|
|||||||
@ -166,6 +166,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
|
|
||||||
def connect_signals_slots(self):
|
def connect_signals_slots(self):
|
||||||
self.actionAdd_file.triggered.connect(self.add_file)
|
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.action_Clear_selection.triggered.connect(self.clear_selection)
|
||||||
self.actionClosePlaylist.triggered.connect(self.close_playlist_tab)
|
self.actionClosePlaylist.triggered.connect(self.close_playlist_tab)
|
||||||
self.actionExport_playlist.triggered.connect(self.export_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.btnSongInfo.clicked.connect(self.song_info_search)
|
||||||
self.btnStop.clicked.connect(self.stop)
|
self.btnStop.clicked.connect(self.stop)
|
||||||
self.spnVolume.valueChanged.connect(self.change_volume)
|
self.spnVolume.valueChanged.connect(self.change_volume)
|
||||||
|
self.tabPlaylist.tabCloseRequested.connect(self.close_tab)
|
||||||
|
|
||||||
self.timer.timeout.connect(self.tick)
|
self.timer.timeout.connect(self.tick)
|
||||||
|
|
||||||
@ -227,6 +229,15 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
index = self.tabPlaylist.currentIndex()
|
index = self.tabPlaylist.currentIndex()
|
||||||
self.tabPlaylist.removeTab(index)
|
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):
|
def create_note(self, session, text):
|
||||||
"""
|
"""
|
||||||
Create note
|
Create note
|
||||||
@ -419,6 +430,9 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
# the playlistis opened.
|
# the playlistis opened.
|
||||||
destination_visible_playlist_tab = None
|
destination_visible_playlist_tab = None
|
||||||
for tab in range(self.tabPlaylist.count()):
|
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:
|
if self.tabPlaylist.widget(tab).id == dlg.plid:
|
||||||
destination_visible_playlist_tab = (
|
destination_visible_playlist_tab = (
|
||||||
self.tabPlaylist.widget(tab))
|
self.tabPlaylist.widget(tab))
|
||||||
@ -511,7 +525,6 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
QColor(Config.COLOUR_NEXT_TAB))
|
QColor(Config.COLOUR_NEXT_TAB))
|
||||||
|
|
||||||
# Tell database to record it as played
|
# Tell database to record it as played
|
||||||
self.current_track.update_lastplayed()
|
|
||||||
Playdates.add_playdate(session, self.current_track)
|
Playdates.add_playdate(session, self.current_track)
|
||||||
|
|
||||||
self.disable_play_next_controls()
|
self.disable_play_next_controls()
|
||||||
|
|||||||
@ -141,6 +141,8 @@ class PlaylistTab(QTableWidget):
|
|||||||
self.setSpan(row, self.COL_NOTE, self.NOTE_ROW_SPAN,
|
self.setSpan(row, self.COL_NOTE, self.NOTE_ROW_SPAN,
|
||||||
self.NOTE_COL_SPAN)
|
self.NOTE_COL_SPAN)
|
||||||
|
|
||||||
|
# Scroll to drop zone
|
||||||
|
self.scrollToItem(self.item(row, 1))
|
||||||
super().dropEvent(event)
|
super().dropEvent(event)
|
||||||
|
|
||||||
DEBUG(
|
DEBUG(
|
||||||
@ -1028,8 +1030,13 @@ class PlaylistTab(QTableWidget):
|
|||||||
if row_time:
|
if row_time:
|
||||||
next_start_time = row_time
|
next_start_time = row_time
|
||||||
# Set colour
|
# 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(
|
self._set_row_colour(
|
||||||
row, QColor(Config.COLOUR_NOTES_PLAYLIST)
|
row, QColor(note_colour)
|
||||||
)
|
)
|
||||||
self._set_row_bold(row)
|
self._set_row_bold(row)
|
||||||
|
|
||||||
@ -1044,6 +1051,10 @@ class PlaylistTab(QTableWidget):
|
|||||||
# Set start time
|
# Set start time
|
||||||
self._set_row_start_time(
|
self._set_row_start_time(
|
||||||
row, self.current_track_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
|
# Calculate next_start_time
|
||||||
next_start_time = self._calculate_next_start_time(
|
next_start_time = self._calculate_next_start_time(
|
||||||
session, row, self.current_track_start_time)
|
session, row, self.current_track_start_time)
|
||||||
|
|||||||
@ -14,11 +14,13 @@ from mutagen.mp3 import MP3
|
|||||||
from pydub import AudioSegment, effects
|
from pydub import AudioSegment, effects
|
||||||
from tinytag import TinyTag
|
from tinytag import TinyTag
|
||||||
|
|
||||||
|
# Globals (I know)
|
||||||
|
messages = []
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
"Main loop"
|
"Main loop"
|
||||||
|
|
||||||
INFO("Starting")
|
DEBUG("Starting")
|
||||||
|
|
||||||
# Parse command line
|
# Parse command line
|
||||||
p = argparse.ArgumentParser()
|
p = argparse.ArgumentParser()
|
||||||
@ -36,25 +38,25 @@ def main():
|
|||||||
|
|
||||||
# Run as required
|
# Run as required
|
||||||
if args.update:
|
if args.update:
|
||||||
INFO("Updating database")
|
DEBUG("Updating database")
|
||||||
with Session() as session:
|
with Session() as session:
|
||||||
update_db(session)
|
update_db(session)
|
||||||
elif args.full_update:
|
elif args.full_update:
|
||||||
INFO("Full update of database")
|
DEBUG("Full update of database")
|
||||||
with Session() as session:
|
with Session() as session:
|
||||||
full_update_db(session)
|
full_update_db(session)
|
||||||
elif args.fname:
|
elif args.fname:
|
||||||
fname = os.path.realpath(args.fname)
|
fname = os.path.realpath(args.fname)
|
||||||
with Session() as session:
|
with Session() as session:
|
||||||
create_track_from_file(session, fname, verbose=True)
|
create_track_from_file(session, fname, interactive=True)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
INFO("No action specified")
|
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
|
Create track in database from passed path, or update database entry
|
||||||
if path already in database.
|
if path already in database.
|
||||||
@ -62,23 +64,35 @@ def create_track_from_file(session, path, verbose=False):
|
|||||||
Return track.
|
Return track.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if verbose:
|
if interactive:
|
||||||
str = f"Importing {path}"
|
str = f"Importing {path}"
|
||||||
INFO(str)
|
INFO(str)
|
||||||
INFO("-" * len(str))
|
INFO("-" * len(str))
|
||||||
track = Tracks.get_or_create(session, path)
|
|
||||||
if verbose:
|
|
||||||
INFO("Get track info...")
|
INFO("Get track info...")
|
||||||
t = get_music_info(path)
|
t = get_music_info(path)
|
||||||
track.title = t['title']
|
title = t['title']
|
||||||
track.artist = t['artist']
|
artist = t['artist']
|
||||||
if verbose:
|
if interactive:
|
||||||
INFO(f" Title: \"{track.title}\"")
|
INFO(f" Title: \"{title}\"")
|
||||||
INFO(f" Artist: \"{track.artist}\"")
|
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(
|
track.duration = int(round(
|
||||||
t['duration'], Config.MILLISECOND_SIGFIGS) * 1000)
|
t['duration'], Config.MILLISECOND_SIGFIGS) * 1000)
|
||||||
|
|
||||||
if verbose:
|
if interactive:
|
||||||
INFO("Parse for start, fade and silence...")
|
INFO("Parse for start, fade and silence...")
|
||||||
audio = get_audio_segment(path)
|
audio = get_audio_segment(path)
|
||||||
track.start_gap = leading_silence(audio)
|
track.start_gap = leading_silence(audio)
|
||||||
@ -90,7 +104,7 @@ def create_track_from_file(session, path, verbose=False):
|
|||||||
session.commit()
|
session.commit()
|
||||||
|
|
||||||
if Config.NORMALISE_ON_IMPORT:
|
if Config.NORMALISE_ON_IMPORT:
|
||||||
if verbose:
|
if interactive:
|
||||||
INFO("Normalise...")
|
INFO("Normalise...")
|
||||||
# Check type
|
# Check type
|
||||||
ftype = os.path.splitext(path)[1][1:]
|
ftype = os.path.splitext(path)[1][1:]
|
||||||
@ -317,8 +331,7 @@ def update_db(session):
|
|||||||
# is filename in database?
|
# is filename in database?
|
||||||
track = Tracks.get_track_from_filename(session, os.path.basename(path))
|
track = Tracks.get_track_from_filename(session, os.path.basename(path))
|
||||||
if not track:
|
if not track:
|
||||||
INFO(f"songdb.update_db: Adding to database: {path}")
|
messages.append(f"Track missing from database: {path}")
|
||||||
create_track_from_file(session, path)
|
|
||||||
else:
|
else:
|
||||||
# Check track info matches found track
|
# Check track info matches found track
|
||||||
t = get_music_info(path)
|
t = get_music_info(path)
|
||||||
@ -333,7 +346,7 @@ def update_db(session):
|
|||||||
for path in list(db_paths - os_paths):
|
for path in list(db_paths - os_paths):
|
||||||
# Manage tracks listed in database but where path is invalid
|
# Manage tracks listed in database but where path is invalid
|
||||||
track = Tracks.get_track_from_path(session, path)
|
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
|
# Remove references from Playdates
|
||||||
Playdates.remove_track(session, track.id)
|
Playdates.remove_track(session, track.id)
|
||||||
@ -352,6 +365,12 @@ def update_db(session):
|
|||||||
# Remove Track entry pointing to invalid path
|
# Remove Track entry pointing to invalid path
|
||||||
Tracks.remove_path(session, 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):
|
def update_meta(session, track, artist=None, title=None):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@ -468,7 +468,7 @@ border: 1px solid rgb(85, 87, 83);</string>
|
|||||||
<bool>false</bool>
|
<bool>false</bool>
|
||||||
</property>
|
</property>
|
||||||
<property name="tabsClosable">
|
<property name="tabsClosable">
|
||||||
<bool>false</bool>
|
<bool>true</bool>
|
||||||
</property>
|
</property>
|
||||||
<property name="movable">
|
<property name="movable">
|
||||||
<bool>true</bool>
|
<bool>true</bool>
|
||||||
@ -770,6 +770,7 @@ border: 1px solid rgb(85, 87, 83);</string>
|
|||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
<addaction name="actionSearch_database"/>
|
<addaction name="actionSearch_database"/>
|
||||||
<addaction name="actionAdd_file"/>
|
<addaction name="actionAdd_file"/>
|
||||||
|
<addaction name="actionAdd_note"/>
|
||||||
<addaction name="action_Clear_selection"/>
|
<addaction name="action_Clear_selection"/>
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
<addaction name="actionSelect_unplayed_tracks"/>
|
<addaction name="actionSelect_unplayed_tracks"/>
|
||||||
@ -1004,6 +1005,14 @@ border: 1px solid rgb(85, 87, 83);</string>
|
|||||||
<string>Select unplayed tracks</string>
|
<string>Select unplayed tracks</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
|
<action name="actionAdd_note">
|
||||||
|
<property name="text">
|
||||||
|
<string>Add note...</string>
|
||||||
|
</property>
|
||||||
|
<property name="shortcut">
|
||||||
|
<string>Ctrl+T</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
</widget>
|
</widget>
|
||||||
<customwidgets>
|
<customwidgets>
|
||||||
<customwidget>
|
<customwidget>
|
||||||
|
|||||||
@ -208,7 +208,7 @@ class Ui_MainWindow(object):
|
|||||||
self.gridLayout_3.addWidget(self.frame_5, 1, 0, 1, 1)
|
self.gridLayout_3.addWidget(self.frame_5, 1, 0, 1, 1)
|
||||||
self.tabPlaylist = QtWidgets.QTabWidget(self.centralwidget)
|
self.tabPlaylist = QtWidgets.QTabWidget(self.centralwidget)
|
||||||
self.tabPlaylist.setDocumentMode(False)
|
self.tabPlaylist.setDocumentMode(False)
|
||||||
self.tabPlaylist.setTabsClosable(False)
|
self.tabPlaylist.setTabsClosable(True)
|
||||||
self.tabPlaylist.setMovable(True)
|
self.tabPlaylist.setMovable(True)
|
||||||
self.tabPlaylist.setObjectName("tabPlaylist")
|
self.tabPlaylist.setObjectName("tabPlaylist")
|
||||||
self.gridLayout_3.addWidget(self.tabPlaylist, 2, 0, 1, 1)
|
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_played_tracks.setObjectName("actionSelect_played_tracks")
|
||||||
self.actionSelect_unplayed_tracks = QtWidgets.QAction(MainWindow)
|
self.actionSelect_unplayed_tracks = QtWidgets.QAction(MainWindow)
|
||||||
self.actionSelect_unplayed_tracks.setObjectName("actionSelect_unplayed_tracks")
|
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.menuFile.addAction(self.actionE_xit)
|
||||||
self.menuPlaylist.addAction(self.actionNewPlaylist)
|
self.menuPlaylist.addAction(self.actionNewPlaylist)
|
||||||
self.menuPlaylist.addAction(self.actionOpenPlaylist)
|
self.menuPlaylist.addAction(self.actionOpenPlaylist)
|
||||||
@ -447,6 +449,7 @@ class Ui_MainWindow(object):
|
|||||||
self.menuPlaylist.addSeparator()
|
self.menuPlaylist.addSeparator()
|
||||||
self.menuPlaylist.addAction(self.actionSearch_database)
|
self.menuPlaylist.addAction(self.actionSearch_database)
|
||||||
self.menuPlaylist.addAction(self.actionAdd_file)
|
self.menuPlaylist.addAction(self.actionAdd_file)
|
||||||
|
self.menuPlaylist.addAction(self.actionAdd_note)
|
||||||
self.menuPlaylist.addAction(self.action_Clear_selection)
|
self.menuPlaylist.addAction(self.action_Clear_selection)
|
||||||
self.menuPlaylist.addSeparator()
|
self.menuPlaylist.addSeparator()
|
||||||
self.menuPlaylist.addAction(self.actionSelect_unplayed_tracks)
|
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_previous_track.setShortcut(_translate("MainWindow", "K"))
|
||||||
self.actionSelect_played_tracks.setText(_translate("MainWindow", "Select played tracks"))
|
self.actionSelect_played_tracks.setText(_translate("MainWindow", "Select played tracks"))
|
||||||
self.actionSelect_unplayed_tracks.setText(_translate("MainWindow", "Select unplayed 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
|
from musicmuster import ElideLabel
|
||||||
import icons_rc
|
import icons_rc
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user