Compare commits

...

24 Commits

Author SHA1 Message Date
Keith Edmunds
ee422aacb3 Update track times after drag and drop 2023-03-12 18:48:36 +00:00
Keith Edmunds
380806d27a Change row to row_number 2023-03-12 18:43:23 +00:00
Keith Edmunds
c6840d2356 Improve copying track path 2023-03-12 18:41:44 +00:00
Keith Edmunds
453e42172b Use Audacity and mplayer without session 2023-03-12 18:41:13 +00:00
Keith Edmunds
019bc87eb0 Fix sense of file_is_unreadable() 2023-03-12 18:38:00 +00:00
Keith Edmunds
ee64a4a035 Remove unused function _get_row_start_time 2023-03-12 17:08:13 +00:00
Keith Edmunds
9c67b9bd8e Change row to row_number 2023-03-12 17:03:47 +00:00
Keith Edmunds
3cc90f8c11 Remove unneeded function _get_current_track_start_time 2023-03-12 16:53:13 +00:00
Keith Edmunds
71daccab12 Remove unneeded function _get_current_track_end_time 2023-03-12 16:51:02 +00:00
Keith Edmunds
ca86f59736 Rename function file_is_readable to file_is_unreadable 2023-03-12 16:47:45 +00:00
Keith Edmunds
d609656ae3 Change row to row_number 2023-03-12 16:44:12 +00:00
Keith Edmunds
f96e02d9ae Remove unused function _deferred_save() 2023-03-12 16:43:51 +00:00
Keith Edmunds
6c53d59f1a Fix adding/removing track from row 2023-03-12 16:23:56 +00:00
Keith Edmunds
b9fd7a5d21 Clean up editing 2023-03-12 16:23:36 +00:00
Keith Edmunds
669125794f Change row to row_number 2023-03-12 16:22:59 +00:00
Keith Edmunds
4094b63f44 Switch to existing infotab if it contains required URL 2023-03-12 13:25:20 +00:00
Keith Edmunds
b126a70139 Fix and improve hide played tracks
Last played track is now not hidden until
Config.HIDE_AFTER_PLAYING_OFFSET milliseconds after next track starts
playing.
2023-03-12 13:00:46 +00:00
Keith Edmunds
f30fff5356 Fix development bug that truncated playlists
Saving of playlist and updating note colours more consistent.
2023-03-12 10:33:10 +00:00
Keith Edmunds
80e698680b Clean up header and note updates
• rationalise number of functions
• make colour handling cleaner
• optimise when playlist is saved
2023-03-12 09:53:11 +00:00
Keith Edmunds
39ec7f470b Fixup section duration times 2023-03-11 16:05:10 +00:00
Keith Edmunds
16ad7ae5aa Produce consistent log output 2023-03-11 16:02:26 +00:00
Keith Edmunds
d54f1bedda Remove colon-in-path conditional fix 2023-03-10 23:11:23 +00:00
Keith Edmunds
ad071bb74b Consistently use clear_next() to clear next track 2023-03-10 23:10:54 +00:00
Keith Edmunds
2422adea21 Fix play sometimes stopping almost immediately 2023-03-10 22:34:23 +00:00
8 changed files with 491 additions and 534 deletions

View File

@ -10,7 +10,6 @@ class Config(object):
CART_DIRECTORY = "/home/kae/radio/CartTracks"
CARTS_COUNT = 10
CARTS_HIDE = True
COLON_IN_PATH_FIX = True
COLOUR_BITRATE_LOW = "#ffcdd2"
COLOUR_BITRATE_MEDIUM = "#ffeb6f"
COLOUR_BITRATE_OK = "#dcedc8"
@ -50,6 +49,7 @@ class Config(object):
ERRORS_TO = ['kae@midnighthax.com']
FADE_STEPS = 20
FADE_TIME = 3000
HIDE_AFTER_PLAYING_OFFSET = 5000
INFO_TAB_TITLE_LENGTH = 15
LAST_PLAYED_TODAY_STRING = "Today"
LOG_LEVEL_STDERR = logging.ERROR
@ -67,6 +67,7 @@ class Config(object):
MINIMUM_ROW_HEIGHT = 30
MYSQL_CONNECT = os.environ.get('MYSQL_CONNECT') or "mysql+mysqldb://musicmuster:musicmuster@localhost/musicmuster_v2" # noqa E501
NOTE_TIME_FORMAT = "%H:%M:%S"
PLAY_SETTLE = 500000
ROOT = os.environ.get('ROOT') or "/home/kae/music"
IMPORT_DESTINATION = os.path.join(ROOT, "Singles")
SCROLL_TOP_MARGIN = 3

View File

@ -55,15 +55,15 @@ def fade_point(
return int(trim_ms)
def file_is_readable(path: Optional[str]) -> bool:
def file_is_unreadable(path: Optional[str]) -> bool:
"""
Returns True if passed path is readable, else False
"""
if not path:
return False
return True
return os.access(path, os.R_OK)
return not os.access(path, os.R_OK)
def get_audio_segment(path: str) -> Optional[AudioSegment]:

View File

@ -20,6 +20,7 @@ class InfoTabs(QTabWidget):
# Dictionary to record when tabs were last updated (so we can
# re-use the oldest one later)
self.last_update: Dict[QWebEngineView, datetime] = {}
self.tabtitles: Dict[int, str] = {}
def open_in_songfacts(self, title):
"""Search Songfacts for title"""
@ -39,10 +40,19 @@ class InfoTabs(QTabWidget):
def open_tab(self, url: str, title: str) -> None:
"""
Open passed URL. Create new tab if we're below the maximum
Open passed URL. If URL currently displayed, switch to that tab.
Create new tab if we're below the maximum
number otherwise reuse oldest content tab.
"""
if url in self.tabtitles.values():
self.setCurrentIndex(
list(self.tabtitles.keys())[
list(self.tabtitles.values()).index(url)
]
)
return
short_title = title[:Config.INFO_TAB_TITLE_LENGTH]
if self.count() < Config.MAX_INFO_TABS:
@ -61,6 +71,7 @@ class InfoTabs(QTabWidget):
widget.setUrl(QUrl(url))
self.last_update[widget] = datetime.now()
self.tabtitles[tab_index] = url
# Show newly updated tab
self.setCurrentIndex(tab_index)

View File

@ -75,7 +75,8 @@ def log_uncaught_exceptions(_ex_cls, ex, tb):
print("\033[1;31;47m")
logging.critical(''.join(traceback.format_tb(tb)))
print("\033[1;37;40m")
print(stackprinter.format(ex, style="darkbg2", add_summary=True))
print(stackprinter.format(ex, show_vals="all", add_summary=True,
style="darkbg"))
if os.environ["MM_ENV"] == "PRODUCTION":
msg = stackprinter.format(ex)
send_mail(Config.ERRORS_TO, Config.ERRORS_FROM,

View File

@ -98,13 +98,13 @@ class NoteColours(Base):
)
@staticmethod
def get_colour(session: scoped_session, text: str) -> str:
def get_colour(session: scoped_session, text: str) -> Optional[str]:
"""
Parse text and return colour string if matched, else empty string
"""
if not text:
return ""
return None
for rec in session.execute(
select(NoteColours)
@ -126,7 +126,7 @@ class NoteColours(Base):
if rec.substring.lower() in text.lower():
return rec.colour
return ""
return None
class Playdates(Base):
@ -464,26 +464,32 @@ class PlaylistRows(Base):
session.commit()
@classmethod
def get_section_header_rows(cls, session: scoped_session,
playlist_id: int) -> List["PlaylistRows"]:
def get_from_id_list(cls, session: scoped_session, playlist_id: int,
plr_ids: List[int]) -> List["PlaylistRows"]:
"""
Return a list of PlaylistRows that are section headers for this
playlist
Take a list of PlaylistRows ids and return a list of corresponding
PlaylistRows objects
"""
plrs = session.execute(
select(cls)
.where(
cls.playlist_id == playlist_id,
cls.track_id.is_(None),
(
cls.note.endswith("-") |
cls.note.endswith("+")
)
cls.id.in_(plr_ids)
).order_by(cls.row_number)).scalars().all()
return plrs
@staticmethod
def get_last_used_row(session: scoped_session,
playlist_id: int) -> Optional[int]:
"""Return the last used row for playlist, or None if no rows"""
return session.execute(
select(func.max(PlaylistRows.row_number))
.where(PlaylistRows.playlist_id == playlist_id)
).scalar_one()
@staticmethod
def get_track_plr(session: scoped_session, track_id: int,
playlist_id: int) -> Optional["PlaylistRows"]:
@ -498,16 +504,6 @@ class PlaylistRows(Base):
.limit(1)
).first()
@staticmethod
def get_last_used_row(session: scoped_session,
playlist_id: int) -> Optional[int]:
"""Return the last used row for playlist, or None if no rows"""
return session.execute(
select(func.max(PlaylistRows.row_number))
.where(PlaylistRows.playlist_id == playlist_id)
).scalar_one()
@classmethod
def get_played_rows(cls, session: scoped_session,
playlist_id: int) -> List["PlaylistRows"]:
@ -572,27 +568,6 @@ class PlaylistRows(Base):
return plrs
@staticmethod
def indexed_by_id(session: scoped_session,
plr_ids: Union[Iterable[int], ValuesView]) -> dict:
"""
Return a dictionary of playlist_rows indexed by their plr id from
the passed plr_id list.
"""
plrs = session.execute(
select(PlaylistRows)
.where(
PlaylistRows.id.in_(plr_ids)
)
).scalars().all()
result = {}
for plr in plrs:
result[plr.id] = plr
return result
@staticmethod
def move_rows_down(session: scoped_session, playlist_id: int,
starting_row: int, move_by: int) -> None:

View File

@ -4,7 +4,7 @@ import vlc # type: ignore
#
from config import Config
from datetime import datetime
from helpers import file_is_readable
from helpers import file_is_unreadable
from typing import Optional
from time import sleep
@ -101,17 +101,14 @@ class Music:
Log and return if path not found.
"""
if not file_is_readable(path):
if file_is_unreadable(path):
log.error(f"play({path}): path not readable")
return None
status = -1
if Config.COLON_IN_PATH_FIX:
media = self.VLC.media_new_path(path)
self.player = media.player_new_from_media()
else:
self.player = self.VLC.media_player_new(path)
if self.player:
self.player.audio_set_volume(self.max_volume)
status = self.player.play()

View File

@ -257,7 +257,6 @@ class MusicMusterSignals(QObject):
emit-a-signal-from-another-class-to-main-class
"""
save_playlist_signal = pyqtSignal()
update_row_note_signal = pyqtSignal(int)
@ -315,7 +314,7 @@ class Window(QMainWindow, Ui_MainWindow):
btn.setEnabled(False)
btn.pgb.setVisible(False)
if cart.path:
if helpers.file_is_readable(cart.path):
if not helpers.file_is_unreadable(cart.path):
colour = Config.COLOUR_CART_READY
btn.path = cart.path
btn.player = self.music.VLC.media_player_new(cart.path)
@ -340,7 +339,7 @@ class Window(QMainWindow, Ui_MainWindow):
if not isinstance(btn, CartButton):
return
if helpers.file_is_readable(btn.path):
if not helpers.file_is_unreadable(btn.path):
# Don't allow clicks while we're playing
btn.setEnabled(False)
if not btn.player:
@ -378,7 +377,7 @@ class Window(QMainWindow, Ui_MainWindow):
if not path:
QMessageBox.warning(self, "Error", "Filename required")
return
if cart.path and helpers.file_is_readable(cart.path):
if cart.path and not helpers.file_is_unreadable(cart.path):
tags = helpers.get_tags(cart.path)
cart.duration = tags['duration']
@ -531,7 +530,7 @@ class Window(QMainWindow, Ui_MainWindow):
# Attempt to close next track playlist
if self.tabPlaylist.widget(tab_index) == self.next_track.playlist_tab:
self.next_track.playlist_tab.mark_unnext()
self.next_track.playlist_tab.clear_next()
# Record playlist as closed and update remaining playlist tabs
with Session() as session:
@ -727,11 +726,9 @@ class Window(QMainWindow, Ui_MainWindow):
Actions required:
- Set flag to say we're not playing a track
- Reset current track
- Tell playlist_tab track has finished
- Reset current playlist_tab
- Reset PlaylistTrack objects
- Reset clocks
- Reset end time
- Update headers
- Enable controls
"""
@ -740,7 +737,7 @@ class Window(QMainWindow, Ui_MainWindow):
# doesn't see player=None and kick off end-of-track actions
self.playing = False
# Remove currently playing track colour
# Tell playlist_tab track has finished
if self.current_track.playlist_tab:
self.current_track.playlist_tab.play_ended()
@ -869,11 +866,8 @@ class Window(QMainWindow, Ui_MainWindow):
self.hide_played_tracks = True
self.btnHidePlayed.setText("Show played")
# Update all displayed playlists
with Session() as session:
for i in range(self.tabPlaylist.count()):
self.tabPlaylist.widget(i).hide_played_tracks(
self.hide_played_tracks)
# Update displayed playlist
self.visible_playlist_tab().hide_or_show_played_tracks()
def import_track(self) -> None:
"""Import track file"""
@ -1272,7 +1266,7 @@ class Window(QMainWindow, Ui_MainWindow):
self.current_track.playlist_tab != self.visible_playlist_tab()
and self.previous_track.plr_id
):
self.previous_track.playlist_tab.reset_next()
self.previous_track.playlist_tab.clear_next()
# Update headers
self.update_headers()
@ -1518,8 +1512,8 @@ class Window(QMainWindow, Ui_MainWindow):
# May also be called when last tab is closed
pass
def this_is_the_next_playlist_row(self, session: scoped_session,
plr: PlaylistRows,
def this_is_the_next_playlist_row(
self, session: scoped_session, plr: PlaylistRows,
next_track_playlist_tab: PlaylistTab) -> None:
"""
This is notification from a playlist tab that it holds the next
@ -1552,7 +1546,7 @@ class Window(QMainWindow, Ui_MainWindow):
# Discard now-incorrect next_track PlaylistTrack and tell
# playlist_tab too
self.next_track.playlist_tab.reset_next()
self.next_track.playlist_tab.clear_next()
self.clear_next()
# Populate self.next_track
@ -1613,7 +1607,14 @@ class Window(QMainWindow, Ui_MainWindow):
return
# If track is playing, update track clocks time and colours
if self.music.player and self.music.player.is_playing():
# There is a discrete time between starting playing a track and
# player.is_playing() returning True, so assume playing if less
# than Config.PLAY_SETTLE microseconds have passed since
# starting play.
if self.music.player and self.current_track.start_time and (
self.music.player.is_playing() or
(datetime.now() - self.current_track.start_time)
< timedelta(microseconds=Config.PLAY_SETTLE)):
playtime = self.music.get_playtime()
time_to_fade = (self.current_track.fade_at - playtime)
time_to_silence = (

File diff suppressed because it is too large Load Diff