Compare commits

..

No commits in common. "d4f542cc292d3519f58501c604064d7ebb272792" and "6336eb921511706e6980a0507a0c937cf6351ab0" have entirely different histories.

5 changed files with 114 additions and 131 deletions

View File

@ -1,5 +1,4 @@
from datetime import datetime from datetime import datetime, date
from PyQt5.QtWidgets import QMessageBox
def get_relative_date(past_date, reference_date=None): def get_relative_date(past_date, reference_date=None):
@ -37,12 +36,6 @@ def get_relative_date(past_date, reference_date=None):
return f"{weeks} {weeks_str}, {days} {days_str} ago" return f"{weeks} {weeks_str}, {days} {days_str} ago"
def show_warning(title, msg):
"Display a warning to user"
QMessageBox.warning(None, title, msg, buttons=QMessageBox.Cancel)
def ms_to_mmss(ms, decimals=0, negative=False): def ms_to_mmss(ms, decimals=0, negative=False):
if not ms: if not ms:
return "-" return "-"

View File

@ -2,7 +2,7 @@
import sqlalchemy import sqlalchemy
from datetime import datetime from datetime import datetime, time
from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import ( from sqlalchemy import (
Boolean, Boolean,
@ -53,8 +53,6 @@ class Notes(Base):
@staticmethod @staticmethod
def add_note(session, playlist_id, row, text): def add_note(session, playlist_id, row, text):
"Add note"
DEBUG(f"add_note(playlist_id={playlist_id}, row={row}, text={text})") DEBUG(f"add_note(playlist_id={playlist_id}, row={row}, text={text})")
note = Notes() note = Notes()
note.playlist_id = playlist_id note.playlist_id = playlist_id
@ -66,13 +64,16 @@ class Notes(Base):
@staticmethod @staticmethod
def delete_note(session, id): def delete_note(session, id):
"Delete note"
DEBUG(f"delete_note(id={id}") DEBUG(f"delete_note(id={id}")
session.query(Notes).filter(Notes.id == id).delete() session.query(Notes).filter(Notes.id == id).delete()
session.commit() session.commit()
# Not currently used 1 June 2021
# @staticmethod
# def get_note(session, id):
# return session.query(Notes).filter(Notes.id == id).one()
@classmethod @classmethod
def update_note(cls, session, id, row, text=None): def update_note(cls, session, id, row, text=None):
""" """
@ -98,8 +99,6 @@ class Playdates(Base):
@staticmethod @staticmethod
def add_playdate(session, track): def add_playdate(session, track):
"Record that track was played"
DEBUG(f"add_playdate(track={track})") DEBUG(f"add_playdate(track={track})")
pd = Playdates() pd = Playdates()
pd.lastplayed = datetime.now() pd.lastplayed = datetime.now()
@ -452,16 +451,6 @@ class Tracks(Base):
f"<Track(id={self.id}, title={self.title}, " f"<Track(id={self.id}, title={self.title}, "
f"artist={self.artist}, path={self.path}>" f"artist={self.artist}, path={self.path}>"
) )
# Not currently used 1 June 2021
# @staticmethod
# def get_note(session, id):
# return session.query(Notes).filter(Notes.id == id).one()
@staticmethod
def get_all_paths(session):
"Return a list of paths of all tracks"
return [a[0] for a in session.query(Tracks.path).all()]
@classmethod @classmethod
def get_or_create(cls, session, path): def get_or_create(cls, session, path):
@ -483,6 +472,53 @@ class Tracks(Base):
ERROR(f"Can't find track id {id}") ERROR(f"Can't find track id {id}")
return None return None
@staticmethod
def get_all_paths(session):
"Return a list of paths of all tracks"
return [a[0] for a in session.query(Tracks.path).all()]
@staticmethod
def get_path(session, id):
try:
return session.query(Tracks.path).filter(Tracks.id == id).one()[0]
except NoResultFound:
ERROR(f"Can't find track id {id}")
return None
@staticmethod
def get_track(session, id):
try:
DEBUG(f"Tracks.get_track(track_id={id})")
track = session.query(Tracks).filter(Tracks.id == id).one()
return track
except NoResultFound:
ERROR(f"get_track({id}): not found")
return None
@staticmethod
def search(session, title=None, artist=None, duration=None):
"""
Return any tracks matching passed criteria
"""
DEBUG(
f"Tracks.search({title=}, {artist=}), {duration=})"
)
if not title and not artist and not duration:
return None
q = session.query(Tracks).filter(False)
if title:
q = q.filter(Tracks.title == title)
if artist:
q = q.filter(Tracks.artist == artist)
if duration:
q = q.filter(Tracks.duration == duration)
return q.all()
@staticmethod @staticmethod
def get_track_from_filename(session, filename): def get_track_from_filename(session, filename):
""" """
@ -510,29 +546,6 @@ class Tracks(Base):
return session.query(Tracks).filter(Tracks.path == path).first() return session.query(Tracks).filter(Tracks.path == path).first()
@staticmethod
def get_path(session, track_id):
"Return path of passed track_id, or None"
try:
return session.query(Tracks.path).filter(
Tracks.id == track_id).one()[0]
except NoResultFound:
ERROR(f"Can't find track id {track_id}")
return None
@staticmethod
def get_track(session, track_id):
"Return track or None"
try:
DEBUG(f"Tracks.get_track(track_id={track_id})")
track = session.query(Tracks).filter(Tracks.id == track_id).one()
return track
except NoResultFound:
ERROR(f"get_track({track_id}): not found")
return None
@staticmethod @staticmethod
def remove_path(session, path): def remove_path(session, path):
"Remove track with passed path from database" "Remove track with passed path from database"
@ -545,29 +558,6 @@ class Tracks(Base):
except IntegrityError as exception: except IntegrityError as exception:
ERROR(f"Can't remove track with {path=} ({exception=})") ERROR(f"Can't remove track with {path=} ({exception=})")
@staticmethod
def search(session, title=None, artist=None, duration=None):
"""
Return any tracks matching passed criteria
"""
DEBUG(
f"Tracks.search({title=}, {artist=}), {duration=})"
)
if not title and not artist and not duration:
return None
q = session.query(Tracks).filter(False)
if title:
q = q.filter(Tracks.title == title)
if artist:
q = q.filter(Tracks.artist == artist)
if duration:
q = q.filter(Tracks.duration == duration)
return q.all()
@staticmethod @staticmethod
def search_titles(session, text): def search_titles(session, text):
return ( return (

View File

@ -8,6 +8,7 @@ import urllib.parse
from datetime import datetime, timedelta from datetime import datetime, timedelta
from log import DEBUG, EXCEPTION from log import DEBUG, EXCEPTION
from slugify import slugify
from PyQt5.QtCore import Qt, QTimer from PyQt5.QtCore import Qt, QTimer
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
@ -17,6 +18,7 @@ from PyQt5.QtWidgets import (
QInputDialog, QInputDialog,
QListWidgetItem, QListWidgetItem,
QMainWindow, QMainWindow,
QMessageBox,
) )
import helpers import helpers
@ -137,10 +139,10 @@ class Window(QMainWindow, Ui_MainWindow):
self.actionOpenPlaylist.triggered.connect(self.open_playlist) self.actionOpenPlaylist.triggered.connect(self.open_playlist)
self.actionPlay_next.triggered.connect(self.play_next) self.actionPlay_next.triggered.connect(self.play_next)
self.actionSearch_database.triggered.connect(self.search_database) self.actionSearch_database.triggered.connect(self.search_database)
self.actionSelect_next_track.triggered.connect(self.select_next_row) self.actionSelect_next_track.triggered.connect(self.select_next_track)
self.actionSelect_played_tracks.triggered.connect(self.select_played) self.actionSelect_played_tracks.triggered.connect(self.select_played)
self.actionSelect_previous_track.triggered.connect( self.actionSelect_previous_track.triggered.connect(
self.select_previous_row) self.select_previous_track)
self.actionSelect_unplayed_tracks.triggered.connect( self.actionSelect_unplayed_tracks.triggered.connect(
self.select_unplayed) self.select_unplayed)
self.actionSetNext.triggered.connect(self.set_next_track) self.actionSetNext.triggered.connect(self.set_next_track)
@ -222,10 +224,6 @@ class Window(QMainWindow, Ui_MainWindow):
def end_of_track_actions(self): def end_of_track_actions(self):
"Clean up after track played" "Clean up after track played"
# Set self.playing to False so that tick() doesn't see
# player=None and kick off end-of-track actions
self.playing = False
# Clean up metadata # Clean up metadata
self.previous_track = self.current_track self.previous_track = self.current_track
if self.current_track_playlist_tab: if self.current_track_playlist_tab:
@ -233,6 +231,7 @@ class Window(QMainWindow, Ui_MainWindow):
self.current_track_playlist_tab.clear_current() self.current_track_playlist_tab.clear_current()
self.current_track_playlist_tab = None self.current_track_playlist_tab = None
self.current_track = None self.current_track = None
self.playing = False
# Clean up display # Clean up display
self.label_end_timer.setText("00:00") self.label_end_timer.setText("00:00")
@ -240,9 +239,6 @@ class Window(QMainWindow, Ui_MainWindow):
self.frame_fade.setStyleSheet("") self.frame_fade.setStyleSheet("")
self.update_headers() self.update_headers()
# Enable controls
self.enable_play_next_controls()
def export_playlist_tab(self): def export_playlist_tab(self):
"Export the current playlist to an m3u file" "Export the current playlist to an m3u file"
@ -289,6 +285,10 @@ class Window(QMainWindow, Ui_MainWindow):
if not self.current_track: if not self.current_track:
return return
# Set self.playing to False so that tick() doesn't see
# player=None and kick off end-of-track actions
self.playing = False
self.enable_play_next_controls()
self.previous_track_position = self.music.fade() self.previous_track_position = self.music.fade()
self.end_of_track_actions() self.end_of_track_actions()
@ -415,15 +415,17 @@ class Window(QMainWindow, Ui_MainWindow):
self.music.play(self.current_track.path) self.music.play(self.current_track.path)
# Update metadata # Update metadata
# Get next track for this playlist. May be None if there is # TODO is this valid if next track is on different playlist?
# no automatic next track, and may later be overriden by
# user selecting a different track on this or another
# playlist.
next_track_id = self.current_track_playlist_tab.play_started() next_track_id = self.current_track_playlist_tab.play_started()
if next_track_id is not None: if next_track_id is not None:
self.next_track = Tracks.get_track(session, next_track_id) self.next_track = Tracks.get_track(session, next_track_id)
self.next_track_playlist_tab = self.current_track_playlist_tab self.next_track_playlist_tab = self.current_track_playlist_tab
# Check we can read it
if not self.file_is_readable(self.next_track.path):
self.show_warning(
"Can't read next track",
self.next_track.path)
else: else:
self.next_track = self.next_track_playlist_tab = None self.next_track = self.next_track_playlist_tab = None
@ -464,20 +466,20 @@ class Window(QMainWindow, Ui_MainWindow):
playlist_db = Playlists.open(session, dlg.plid) playlist_db = Playlists.open(session, dlg.plid)
self.load_playlist(session, playlist_db) self.load_playlist(session, playlist_db)
def select_next_row(self): def select_next_track(self):
"Select next or first row in playlist" "Select next or first track in playlist"
self.visible_playlist_tab().select_next_row() self.visible_playlist_tab().select_next_track()
def select_played(self): def select_played(self):
"Select all played tracks in playlist" "Select all played tracks in playlist"
self.visible_playlist_tab().select_played_tracks() self.visible_playlist_tab().select_played_tracks()
def select_previous_row(self): def select_previous_track(self):
"Select previous or first row in playlist" "Select previous or first track in playlist"
self.visible_playlist_tab().select_previous_row() self.visible_playlist_tab().select_previous_track()
def set_next_track(self, next_track_id=None): def set_next_track(self, next_track_id=None):
"Set selected track as next" "Set selected track as next"
@ -499,6 +501,11 @@ class Window(QMainWindow, Ui_MainWindow):
self.visible_playlist_tab().select_unplayed_tracks() self.visible_playlist_tab().select_unplayed_tracks()
def show_warning(self, title, msg):
"Display a warning to user"
QMessageBox.warning(None, title, msg, buttons=QMessageBox.Cancel)
def song_info_search(self): def song_info_search(self):
""" """
Open browser tabs for Wikipedia, searching for Open browser tabs for Wikipedia, searching for
@ -527,11 +534,12 @@ class Window(QMainWindow, Ui_MainWindow):
DEBUG("musicmuster.stop()") DEBUG("musicmuster.stop()")
self.stop_playing(fade=False) self.stop_playing(fade=False)
self.enable_play_next_controls()
def stop_playing(self, fade=True): def stop_playing(self, fade=True):
"Stop playing current track" "Stop playing current track"
DEBUG(f"musicmuster.stop_playing({fade=})", True) DEBUG("musicmuster.stop_playing()", True)
if not self.music.playing(): if not self.music.playing():
DEBUG("musicmuster.stop_playing(): not playing", True) DEBUG("musicmuster.stop_playing(): not playing", True)
@ -543,8 +551,8 @@ class Window(QMainWindow, Ui_MainWindow):
DEBUG("musicmuster.stop_playing(): fading music", True) DEBUG("musicmuster.stop_playing(): fading music", True)
self.music.fade() self.music.fade()
else: else:
DEBUG("musicmuster.stop_playing(): stopping music", True)
self.music.stop() self.music.stop()
DEBUG("musicmuster.stop_playing(): stopping music", True)
self.end_of_track_actions() self.end_of_track_actions()
@ -612,20 +620,16 @@ class Window(QMainWindow, Ui_MainWindow):
# Time to fade # Time to fade
self.label_fade_timer.setText(helpers.ms_to_mmss(time_to_fade)) self.label_fade_timer.setText(helpers.ms_to_mmss(time_to_fade))
# If silent in the next 5 seconds, put warning colour on # Time to silence
# time to silence box and enable play controls
if time_to_silence <= 5500: if time_to_silence <= 5500:
self.frame_silent.setStyleSheet( self.frame_silent.setStyleSheet(
f"background: {Config.COLOUR_ENDING_TIMER}" f"background: {Config.COLOUR_ENDING_TIMER}"
) )
self.enable_play_next_controls() self.enable_play_next_controls()
# Set warning colour on time to silence box when fade starts
elif time_to_fade <= 500: elif time_to_fade <= 500:
self.frame_silent.setStyleSheet( self.frame_silent.setStyleSheet(
f"background: {Config.COLOUR_WARNING_TIMER}" f"background: {Config.COLOUR_WARNING_TIMER}"
) )
# Five seconds before fade starts, set warning colour on
# time to silence box and enable play controls
elif time_to_fade <= 5500: elif time_to_fade <= 5500:
self.frame_fade.setStyleSheet( self.frame_fade.setStyleSheet(
f"background: {Config.COLOUR_WARNING_TIMER}" f"background: {Config.COLOUR_WARNING_TIMER}"

View File

@ -16,7 +16,7 @@ import os
from config import Config from config import Config
from datetime import datetime, timedelta from datetime import datetime, timedelta
from helpers import get_relative_date, show_warning from helpers import get_relative_date
from log import DEBUG, ERROR from log import DEBUG, ERROR
from model import ( from model import (
Notes, Playdates, Playlists, PlaylistTracks, Session, Settings, Tracks Notes, Playdates, Playlists, PlaylistTracks, Session, Settings, Tracks
@ -155,8 +155,8 @@ class PlaylistTab(QTableWidget):
self.menu.addSeparator() self.menu.addSeparator()
act_delete = self.menu.addAction('Delete') act_delete = self.menu.addAction('Delete')
act_delete.triggered.connect(lambda: self._delete_row(row)) act_delete.triggered.connect(lambda: self._delete_row(row))
act_info = self.menu.addAction('Info') act_delete = self.menu.addAction('Info')
act_info.triggered.connect(lambda: self._info_row(row)) act_delete.triggered.connect(lambda: self._info_row(row))
return super(PlaylistTab, self).eventFilter(source, event) return super(PlaylistTab, self).eventFilter(source, event)
@ -357,10 +357,7 @@ class PlaylistTab(QTableWidget):
# Scroll to put current track in centre # Scroll to put current track in centre
scroll_to = self.item(current_row, self.COL_INDEX) scroll_to = self.item(current_row, self.COL_INDEX)
self.scrollToItem(scroll_to, QAbstractItemView.PositionAtCenter) self.scrollToItem(scroll_to, QAbstractItemView.PositionAtCenter)
next_track_id = self._mark_next_track()
# Get next track
next_track_row = self._find_next_track_row()
next_track_id = self._set_next(next_track_row)
self._repaint() self._repaint()
return next_track_id return next_track_id
@ -410,9 +407,9 @@ class PlaylistTab(QTableWidget):
# Called when we change tabs # Called when we change tabs
self._repaint() self._repaint()
def select_next_row(self): def select_next_track(self):
""" """
Select next or first row. Don't select notes. Wrap at last row. Select next or first track. Don't select notes. Wrap at last row.
""" """
selected_rows = [row for row in selected_rows = [row for row in
@ -457,7 +454,7 @@ class PlaylistTab(QTableWidget):
# Reset extended selection # Reset extended selection
self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection) self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
def select_previous_row(self): def select_previous_track(self):
""" """
Select previous or last track. Don't select notes. Wrap at first row. Select previous or last track. Don't select notes. Wrap at first row.
""" """
@ -517,8 +514,7 @@ class PlaylistTab(QTableWidget):
if not self.selectionModel().hasSelection(): if not self.selectionModel().hasSelection():
return return
self._set_next(self.currentRow()) return self._set_next(self.currentRow())
self._repaint()
# ########## Internally called functions ########## # ########## Internally called functions ##########
@ -549,10 +545,12 @@ class PlaylistTab(QTableWidget):
DEBUG(f"playlist._delete_row({row})") DEBUG(f"playlist._delete_row({row})")
if row == self._meta_get_current(): if row == self._meta_get_current():
show_warning("Silly", "Can't delete playing track") # TODO
DEBUG("playlist._delete_row(): Can't delete playing track")
return return
elif row == self._meta_get_next(): elif row == self._meta_get_next():
show_warning("Safety", "Can't delete next track") # TODO
DEBUG("playlist._delete_row(): Can't delete next track")
return return
with Session() as session: with Session() as session:
@ -650,13 +648,13 @@ class PlaylistTab(QTableWidget):
and pos.y() >= rect.center().y() # noqa W503 and pos.y() >= rect.center().y() # noqa W503
) )
def _find_next_track_row(self): def _mark_next_track(self):
""" """
Find next track to play. Find next track to play.
If not found, return None. If not found, return None.
If found, return row number. If found, mark row with metadata and return track_id.
""" """
found_next_track = False found_next_track = False
@ -669,14 +667,16 @@ class PlaylistTab(QTableWidget):
for row in range(start, self.rowCount()): for row in range(start, self.rowCount()):
if row in notes_rows: if row in notes_rows:
continue continue
self._meta_set_next(row)
found_next_track = True found_next_track = True
break break
if found_next_track: if not found_next_track:
return row
else:
return None return None
track_id = self._get_row_id(row)
return track_id
def _meta_clear(self, row): def _meta_clear(self, row):
"Clear metadata for row" "Clear metadata for row"
@ -813,12 +813,11 @@ class PlaylistTab(QTableWidget):
track_id = self._get_row_id(row) track_id = self._get_row_id(row)
if track_id: if track_id:
if self._track_path_is_readable(track_id): if self._track_path_is_readable(track_id):
self._meta_set_next(row) self._meta_set_next(self.currentRow())
self.master_process.set_next_track(track_id) self.master_process.set_next_track(track_id)
else: else:
self._meta_set_unreadable(row) self._meta_set_unreadable(self.currentRow())
track_id = None self._repaint()
return track_id
def _repaint(self, clear_selection=True): def _repaint(self, clear_selection=True):
"Set row colours, fonts, etc" "Set row colours, fonts, etc"

View File

@ -31,15 +31,12 @@ def fade_point(audio_segment, fade_threshold=-12, chunk_size=10):
print(f"Fade last {int(segment_length - trim_ms)/1000} seconds") print(f"Fade last {int(segment_length - trim_ms)/1000} seconds")
# print("Shout:") print("Shout:")
# segment = AudioSegment.from_mp3("../archive/shout.mp3") segment = AudioSegment.from_mp3("../archive/shout.mp3")
# fade_point(segment) fade_point(segment)
# print("Champagne:") print("Champagne:")
# segment = AudioSegment.from_mp3("../archive/champ.mp3") segment = AudioSegment.from_mp3("../archive/champ.mp3")
# fade_point(segment) fade_point(segment)
# print("Be good:") print("Be good:")
# segment = AudioSegment.from_mp3("../archive/wibg.mp3") segment = AudioSegment.from_mp3("../archive/wibg.mp3")
# fade_point(segment)
print("Be good:")
segment = AudioSegment.from_file("/tmp/bia.flac", "flac")
fade_point(segment) fade_point(segment)