Compare commits

..

No commits in common. "ee422aacb39afa0135aedb8fd85356738e63a7ba" and "ee7436221e784ec95cda68b330b31ca87903bafa" have entirely different histories.

8 changed files with 535 additions and 492 deletions

View File

@ -10,6 +10,7 @@ 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"
@ -49,7 +50,6 @@ 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,7 +67,6 @@ 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_unreadable(path: Optional[str]) -> bool:
def file_is_readable(path: Optional[str]) -> bool:
"""
Returns True if passed path is readable, else False
"""
if not path:
return True
return False
return not os.access(path, os.R_OK)
return os.access(path, os.R_OK)
def get_audio_segment(path: str) -> Optional[AudioSegment]:

View File

@ -20,7 +20,6 @@ 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"""
@ -40,19 +39,10 @@ class InfoTabs(QTabWidget):
def open_tab(self, url: str, title: str) -> None:
"""
Open passed URL. If URL currently displayed, switch to that tab.
Create new tab if we're below the maximum
Open passed URL. 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:
@ -71,7 +61,6 @@ 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,8 +75,7 @@ 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, show_vals="all", add_summary=True,
style="darkbg"))
print(stackprinter.format(ex, style="darkbg2", add_summary=True))
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) -> Optional[str]:
def get_colour(session: scoped_session, text: str) -> str:
"""
Parse text and return colour string if matched, else empty string
"""
if not text:
return None
return ""
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 None
return ""
class Playdates(Base):
@ -464,32 +464,26 @@ class PlaylistRows(Base):
session.commit()
@classmethod
def get_from_id_list(cls, session: scoped_session, playlist_id: int,
plr_ids: List[int]) -> List["PlaylistRows"]:
def get_section_header_rows(cls, session: scoped_session,
playlist_id: int) -> List["PlaylistRows"]:
"""
Take a list of PlaylistRows ids and return a list of corresponding
PlaylistRows objects
Return a list of PlaylistRows that are section headers for this
playlist
"""
plrs = session.execute(
select(cls)
.where(
cls.playlist_id == playlist_id,
cls.id.in_(plr_ids)
cls.track_id.is_(None),
(
cls.note.endswith("-") |
cls.note.endswith("+")
)
).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"]:
@ -504,6 +498,16 @@ 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"]:
@ -568,6 +572,27 @@ 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_unreadable
from helpers import file_is_readable
from typing import Optional
from time import sleep
@ -101,14 +101,17 @@ class Music:
Log and return if path not found.
"""
if file_is_unreadable(path):
if not file_is_readable(path):
log.error(f"play({path}): path not readable")
return None
status = -1
media = self.VLC.media_new_path(path)
self.player = media.player_new_from_media()
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,6 +257,7 @@ class MusicMusterSignals(QObject):
emit-a-signal-from-another-class-to-main-class
"""
save_playlist_signal = pyqtSignal()
update_row_note_signal = pyqtSignal(int)
@ -314,7 +315,7 @@ class Window(QMainWindow, Ui_MainWindow):
btn.setEnabled(False)
btn.pgb.setVisible(False)
if cart.path:
if not helpers.file_is_unreadable(cart.path):
if helpers.file_is_readable(cart.path):
colour = Config.COLOUR_CART_READY
btn.path = cart.path
btn.player = self.music.VLC.media_player_new(cart.path)
@ -339,7 +340,7 @@ class Window(QMainWindow, Ui_MainWindow):
if not isinstance(btn, CartButton):
return
if not helpers.file_is_unreadable(btn.path):
if helpers.file_is_readable(btn.path):
# Don't allow clicks while we're playing
btn.setEnabled(False)
if not btn.player:
@ -377,7 +378,7 @@ class Window(QMainWindow, Ui_MainWindow):
if not path:
QMessageBox.warning(self, "Error", "Filename required")
return
if cart.path and not helpers.file_is_unreadable(cart.path):
if cart.path and helpers.file_is_readable(cart.path):
tags = helpers.get_tags(cart.path)
cart.duration = tags['duration']
@ -530,7 +531,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.clear_next()
self.next_track.playlist_tab.mark_unnext()
# Record playlist as closed and update remaining playlist tabs
with Session() as session:
@ -726,9 +727,11 @@ 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 PlaylistTrack objects
- Reset current playlist_tab
- Reset clocks
- Reset end time
- Update headers
- Enable controls
"""
@ -737,7 +740,7 @@ class Window(QMainWindow, Ui_MainWindow):
# doesn't see player=None and kick off end-of-track actions
self.playing = False
# Tell playlist_tab track has finished
# Remove currently playing track colour
if self.current_track.playlist_tab:
self.current_track.playlist_tab.play_ended()
@ -866,8 +869,11 @@ class Window(QMainWindow, Ui_MainWindow):
self.hide_played_tracks = True
self.btnHidePlayed.setText("Show played")
# Update displayed playlist
self.visible_playlist_tab().hide_or_show_played_tracks()
# 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)
def import_track(self) -> None:
"""Import track file"""
@ -1266,7 +1272,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.clear_next()
self.previous_track.playlist_tab.reset_next()
# Update headers
self.update_headers()
@ -1512,9 +1518,9 @@ 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,
next_track_playlist_tab: PlaylistTab) -> None:
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
playlist row to be played.
@ -1546,7 +1552,7 @@ class Window(QMainWindow, Ui_MainWindow):
# Discard now-incorrect next_track PlaylistTrack and tell
# playlist_tab too
self.next_track.playlist_tab.clear_next()
self.next_track.playlist_tab.reset_next()
self.clear_next()
# Populate self.next_track
@ -1607,14 +1613,7 @@ class Window(QMainWindow, Ui_MainWindow):
return
# If track is playing, update track clocks time and colours
# 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)):
if self.music.player and self.music.player.is_playing():
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