Compare commits
No commits in common. "57f038c704fa6474e4ab43cc61602644b447eb9c" and "30bd23c0886d42135c09d3f86f274ef9c1d5c89c" have entirely different histories.
57f038c704
...
30bd23c088
5
.envrc
5
.envrc
@ -1,9 +1,4 @@
|
|||||||
layout poetry
|
layout poetry
|
||||||
export MAIL_PASSWORD="ewacyay5seu2qske"
|
|
||||||
export MAIL_PORT=587
|
|
||||||
export MAIL_SERVER="smtp.fastmail.com"
|
|
||||||
export MAIL_USERNAME="kae@midnighthax.com"
|
|
||||||
export MAIL_USE_TLS=True
|
|
||||||
branch=$(git branch --show-current)
|
branch=$(git branch --show-current)
|
||||||
if on_git_branch master; then
|
if on_git_branch master; then
|
||||||
export MM_ENV="PRODUCTION"
|
export MM_ENV="PRODUCTION"
|
||||||
|
|||||||
@ -12,7 +12,6 @@ class Config(object):
|
|||||||
CART_DIRECTORY = "/home/kae/radio/CartTracks"
|
CART_DIRECTORY = "/home/kae/radio/CartTracks"
|
||||||
CARTS_COUNT = 10
|
CARTS_COUNT = 10
|
||||||
CARTS_HIDE = True
|
CARTS_HIDE = True
|
||||||
COLON_IN_PATH_FIX = True
|
|
||||||
COLOUR_BITRATE_LOW = "#ffcdd2"
|
COLOUR_BITRATE_LOW = "#ffcdd2"
|
||||||
COLOUR_BITRATE_MEDIUM = "#ffeb6f"
|
COLOUR_BITRATE_MEDIUM = "#ffeb6f"
|
||||||
COLOUR_BITRATE_OK = "#dcedc8"
|
COLOUR_BITRATE_OK = "#dcedc8"
|
||||||
@ -54,7 +53,6 @@ class Config(object):
|
|||||||
DEFAULT_IMPORT_DIRECTORY = "/home/kae/Nextcloud/tmp"
|
DEFAULT_IMPORT_DIRECTORY = "/home/kae/Nextcloud/tmp"
|
||||||
DEFAULT_OUTPUT_DIRECTORY = "/home/kae/music/Singles"
|
DEFAULT_OUTPUT_DIRECTORY = "/home/kae/music/Singles"
|
||||||
DISPLAY_SQL = False
|
DISPLAY_SQL = False
|
||||||
ERRORS_FROM = ['noreply@midnighthax.com']
|
|
||||||
ERRORS_TO = ['kae@midnighthax.com']
|
ERRORS_TO = ['kae@midnighthax.com']
|
||||||
FADE_STEPS = 20
|
FADE_STEPS = 20
|
||||||
FADE_TIME = 3000
|
FADE_TIME = 3000
|
||||||
|
|||||||
@ -1,11 +1,8 @@
|
|||||||
import os
|
import os
|
||||||
import psutil
|
import psutil
|
||||||
import shutil
|
import shutil
|
||||||
import smtplib
|
|
||||||
import ssl
|
|
||||||
import tempfile
|
import tempfile
|
||||||
|
|
||||||
from email.message import EmailMessage
|
|
||||||
from mutagen.flac import FLAC # type: ignore
|
from mutagen.flac import FLAC # type: ignore
|
||||||
from mutagen.mp3 import MP3 # type: ignore
|
from mutagen.mp3 import MP3 # type: ignore
|
||||||
from pydub import effects
|
from pydub import effects
|
||||||
@ -59,14 +56,20 @@ def fade_point(
|
|||||||
return int(trim_ms)
|
return int(trim_ms)
|
||||||
|
|
||||||
|
|
||||||
def file_is_readable(path: str) -> bool:
|
def file_is_readable(path: str, check_colon: bool = True) -> bool:
|
||||||
"""
|
"""
|
||||||
Returns True if passed path is readable, else False
|
Returns True if passed path is readable, else False
|
||||||
|
|
||||||
vlc cannot read files with a colon in the path
|
vlc cannot read files with a colon in the path
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return os.access(path, os.R_OK)
|
if os.access(path, os.R_OK):
|
||||||
|
if check_colon:
|
||||||
|
return ':' not in path
|
||||||
|
else:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def get_audio_segment(path: str) -> Optional[AudioSegment]:
|
def get_audio_segment(path: str) -> Optional[AudioSegment]:
|
||||||
@ -162,32 +165,6 @@ def leading_silence(
|
|||||||
return min(trim_ms, len(audio_segment))
|
return min(trim_ms, len(audio_segment))
|
||||||
|
|
||||||
|
|
||||||
def send_mail(to_addr, from_addr, subj, body):
|
|
||||||
# From https://docs.python.org/3/library/email.examples.html
|
|
||||||
|
|
||||||
# Create a text/plain message
|
|
||||||
msg = EmailMessage()
|
|
||||||
msg.set_content(body)
|
|
||||||
|
|
||||||
msg['Subject'] = subj
|
|
||||||
msg['From'] = from_addr
|
|
||||||
msg['To'] = to_addr
|
|
||||||
|
|
||||||
# Send the message via SMTP server.
|
|
||||||
context = ssl.create_default_context()
|
|
||||||
try:
|
|
||||||
s = smtplib.SMTP(host=Config.MAIL_SERVER, port=Config.MAIL_PORT)
|
|
||||||
if Config.MAIL_USE_TLS:
|
|
||||||
s.starttls(context=context)
|
|
||||||
if Config.MAIL_USERNAME and Config.MAIL_PASSWORD:
|
|
||||||
s.login(Config.MAIL_USERNAME, Config.MAIL_PASSWORD)
|
|
||||||
s.send_message(msg)
|
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
finally:
|
|
||||||
s.quit()
|
|
||||||
|
|
||||||
|
|
||||||
def ms_to_mmss(ms: int, decimals: int = 0, negative: bool = False) -> str:
|
def ms_to_mmss(ms: int, decimals: int = 0, negative: bool = False) -> str:
|
||||||
"""Convert milliseconds to mm:ss"""
|
"""Convert milliseconds to mm:ss"""
|
||||||
|
|
||||||
|
|||||||
37
app/log.py
37
app/log.py
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
import logging.handlers
|
import logging.handlers
|
||||||
import stackprinter
|
|
||||||
import sys
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
@ -55,24 +54,26 @@ syslog.addFilter(local_filter)
|
|||||||
stderr.addFilter(local_filter)
|
stderr.addFilter(local_filter)
|
||||||
stderr.addFilter(debug_filter)
|
stderr.addFilter(debug_filter)
|
||||||
|
|
||||||
|
# create formatter and add it to the handlers
|
||||||
class VerboseExceptionFormatter(logging.Formatter):
|
stderr_fmt = logging.Formatter('[%(asctime)s] %(leveltag)s: %(message)s',
|
||||||
def formatException(self, exc_info):
|
datefmt='%H:%M:%S')
|
||||||
msg = stackprinter.format(exc_info)
|
syslog_fmt = logging.Formatter(
|
||||||
lines = msg.split('\n')
|
'[%(name)s] %(module)s.%(funcName)s - %(leveltag)s: %(message)s'
|
||||||
lines_indented = [" ┆ " + line + "\n" for line in lines]
|
)
|
||||||
msg_indented = "".join(lines_indented)
|
stderr.setFormatter(stderr_fmt)
|
||||||
return msg_indented
|
syslog.setFormatter(syslog_fmt)
|
||||||
|
|
||||||
|
|
||||||
stderr_fmt = '[%(asctime)s] %(leveltag)s: %(message)s'
|
|
||||||
stderr_formatter = VerboseExceptionFormatter(stderr_fmt, datefmt='%H:%M:%S')
|
|
||||||
stderr.setFormatter(stderr_formatter)
|
|
||||||
|
|
||||||
syslog_fmt = '[%(name)s] %(module)s.%(funcName)s - %(leveltag)s: %(message)s'
|
|
||||||
syslog_formatter = VerboseExceptionFormatter(syslog_fmt)
|
|
||||||
syslog.setFormatter(syslog_formatter)
|
|
||||||
|
|
||||||
# add the handlers to the log
|
# add the handlers to the log
|
||||||
log.addHandler(stderr)
|
log.addHandler(stderr)
|
||||||
log.addHandler(syslog)
|
log.addHandler(syslog)
|
||||||
|
|
||||||
|
|
||||||
|
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")
|
||||||
|
logging.critical('{0}: {1}'.format(ex_cls, ex))
|
||||||
|
|
||||||
|
|
||||||
|
sys.excepthook = log_uncaught_exceptions
|
||||||
|
|||||||
@ -23,7 +23,6 @@ from sqlalchemy import (
|
|||||||
select,
|
select,
|
||||||
String,
|
String,
|
||||||
UniqueConstraint,
|
UniqueConstraint,
|
||||||
update,
|
|
||||||
)
|
)
|
||||||
# from sqlalchemy.exc import IntegrityError
|
# from sqlalchemy.exc import IntegrityError
|
||||||
from sqlalchemy.orm import (
|
from sqlalchemy.orm import (
|
||||||
@ -556,23 +555,6 @@ class PlaylistRows(Base):
|
|||||||
|
|
||||||
return plrs
|
return plrs
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def move_rows_down(session: Session, playlist_id: int, starting_row: int,
|
|
||||||
move_by: int) -> None:
|
|
||||||
"""
|
|
||||||
Create space to insert move_by additional rows by incremented row
|
|
||||||
number from starting_row to end of playlist
|
|
||||||
"""
|
|
||||||
|
|
||||||
session.execute(
|
|
||||||
update(PlaylistRows)
|
|
||||||
.where(
|
|
||||||
(PlaylistRows.playlist_id == playlist_id),
|
|
||||||
(PlaylistRows.row_number >= starting_row)
|
|
||||||
)
|
|
||||||
.values(row_number=PlaylistRows.row_number + move_by)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Settings(Base):
|
class Settings(Base):
|
||||||
"""Manage settings"""
|
"""Manage settings"""
|
||||||
|
|||||||
@ -111,11 +111,7 @@ class Music:
|
|||||||
status = -1
|
status = -1
|
||||||
self.track_path = path
|
self.track_path = path
|
||||||
|
|
||||||
if Config.COLON_IN_PATH_FIX:
|
self.player = self.VLC.media_player_new(path)
|
||||||
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:
|
if self.player:
|
||||||
self.player.audio_set_volume(self.max_volume)
|
self.player.audio_set_volume(self.max_volume)
|
||||||
self.current_track_start_time = datetime.now()
|
self.current_track_start_time = datetime.now()
|
||||||
|
|||||||
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
from log import log
|
from log import log
|
||||||
import argparse
|
import argparse
|
||||||
import stackprinter
|
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
@ -142,7 +141,6 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
self.next_track_playlist_tab: Optional[PlaylistTab] = None
|
self.next_track_playlist_tab: Optional[PlaylistTab] = None
|
||||||
self.previous_track: Optional[TrackData] = None
|
self.previous_track: Optional[TrackData] = None
|
||||||
self.previous_track_position: Optional[int] = None
|
self.previous_track_position: Optional[int] = None
|
||||||
self.selected_plrs = None
|
|
||||||
|
|
||||||
# Set colours that will be used by playlist row stripes
|
# Set colours that will be used by playlist row stripes
|
||||||
palette = QPalette()
|
palette = QPalette()
|
||||||
@ -393,12 +391,10 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
self.actionImport.triggered.connect(self.import_track)
|
self.actionImport.triggered.connect(self.import_track)
|
||||||
self.actionInsertSectionHeader.triggered.connect(self.insert_header)
|
self.actionInsertSectionHeader.triggered.connect(self.insert_header)
|
||||||
self.actionInsertTrack.triggered.connect(self.insert_track)
|
self.actionInsertTrack.triggered.connect(self.insert_track)
|
||||||
self.actionMark_for_moving.triggered.connect(self.cut_rows)
|
|
||||||
self.actionMoveSelected.triggered.connect(self.move_selected)
|
self.actionMoveSelected.triggered.connect(self.move_selected)
|
||||||
self.actionNew_from_template.triggered.connect(self.new_from_template)
|
self.actionNew_from_template.triggered.connect(self.new_from_template)
|
||||||
self.actionNewPlaylist.triggered.connect(self.create_and_show_playlist)
|
self.actionNewPlaylist.triggered.connect(self.create_and_show_playlist)
|
||||||
self.actionOpenPlaylist.triggered.connect(self.open_playlist)
|
self.actionOpenPlaylist.triggered.connect(self.open_playlist)
|
||||||
self.actionPaste.triggered.connect(self.paste_rows)
|
|
||||||
self.actionPlay_next.triggered.connect(self.play_next)
|
self.actionPlay_next.triggered.connect(self.play_next)
|
||||||
self.actionSave_as_template.triggered.connect(self.save_as_template)
|
self.actionSave_as_template.triggered.connect(self.save_as_template)
|
||||||
self.actionSearch.triggered.connect(self.search_playlist)
|
self.actionSearch.triggered.connect(self.search_playlist)
|
||||||
@ -453,17 +449,6 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
idx = self.tabPlaylist.addTab(playlist_tab, playlist.name)
|
idx = self.tabPlaylist.addTab(playlist_tab, playlist.name)
|
||||||
self.tabPlaylist.setCurrentIndex(idx)
|
self.tabPlaylist.setCurrentIndex(idx)
|
||||||
|
|
||||||
def cut_rows(self) -> None:
|
|
||||||
"""
|
|
||||||
Cut rows ready for pasting.
|
|
||||||
"""
|
|
||||||
|
|
||||||
with Session() as session:
|
|
||||||
# Save the selected PlaylistRows items ready for a later
|
|
||||||
# paste
|
|
||||||
self.selected_plrs = (
|
|
||||||
self.visible_playlist_tab().get_selected_playlistrows(session))
|
|
||||||
|
|
||||||
def debug(self):
|
def debug(self):
|
||||||
"""Invoke debugger"""
|
"""Invoke debugger"""
|
||||||
|
|
||||||
@ -841,11 +826,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
with Session() as session:
|
with Session() as session:
|
||||||
unplayed_playlist_rows = PlaylistRows.get_unplayed_rows(
|
unplayed_playlist_rows = PlaylistRows.get_unplayed_rows(
|
||||||
session, playlist_id)
|
session, playlist_id)
|
||||||
if helpers.ask_yes_no("Move tracks",
|
self.move_playlist_rows(session, unplayed_playlist_rows)
|
||||||
f"Move {len(unplayed_playlist_rows)} tracks:"
|
|
||||||
" Are you sure?"
|
|
||||||
):
|
|
||||||
self.move_playlist_rows(session, unplayed_playlist_rows)
|
|
||||||
|
|
||||||
def new_from_template(self) -> None:
|
def new_from_template(self) -> None:
|
||||||
"""Create new playlist from template"""
|
"""Create new playlist from template"""
|
||||||
@ -878,68 +859,6 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
playlist.mark_open(session)
|
playlist.mark_open(session)
|
||||||
self.create_playlist_tab(session, playlist)
|
self.create_playlist_tab(session, playlist)
|
||||||
|
|
||||||
def paste_rows(self) -> None:
|
|
||||||
"""
|
|
||||||
Paste earlier cut rows.
|
|
||||||
|
|
||||||
Process:
|
|
||||||
- ensure we have some cut rows
|
|
||||||
- if not pasting at end of playlist, move later rows down
|
|
||||||
- update plrs with correct playlist and row
|
|
||||||
- if moving between playlists: renumber source playlist rows
|
|
||||||
- else: check integrity of playlist rows
|
|
||||||
"""
|
|
||||||
|
|
||||||
if not self.selected_plrs:
|
|
||||||
return
|
|
||||||
|
|
||||||
playlist_tab = self.visible_playlist_tab()
|
|
||||||
dst_playlist_id = playlist_tab.playlist_id
|
|
||||||
|
|
||||||
with Session() as session:
|
|
||||||
# Create space in destination playlist
|
|
||||||
if playlist_tab.selectionModel().hasSelection():
|
|
||||||
row = playlist_tab.currentRow()
|
|
||||||
PlaylistRows.move_rows_down(session, dst_playlist_id,
|
|
||||||
row, len(self.selected_plrs))
|
|
||||||
session.commit()
|
|
||||||
|
|
||||||
src_playlist_id = None
|
|
||||||
dst_row = row
|
|
||||||
for plr in self.selected_plrs:
|
|
||||||
# Update moved rows
|
|
||||||
session.add(plr)
|
|
||||||
if not src_playlist_id:
|
|
||||||
src_playlist_id = plr.playlist_id
|
|
||||||
plr.playlist_id = dst_playlist_id
|
|
||||||
plr.row_number = row
|
|
||||||
row += 1
|
|
||||||
# Need to commit each row individually else only one row
|
|
||||||
# gets updated (don't know why)
|
|
||||||
|
|
||||||
session.commit()
|
|
||||||
|
|
||||||
# Update display
|
|
||||||
self.visible_playlist_tab().populate(session, dst_playlist_id)
|
|
||||||
|
|
||||||
# If source playlist is not destination playlist, fixup row
|
|
||||||
# numbers and update display
|
|
||||||
if src_playlist_id != dst_playlist_id:
|
|
||||||
PlaylistRows.fixup_rownumbers(session, src_playlist_id)
|
|
||||||
# Update source playlist_tab if visible (if not visible, it
|
|
||||||
# will be re-populated when it is opened)
|
|
||||||
source_playlist_tab = None
|
|
||||||
for tab in range(self.tabPlaylist.count()):
|
|
||||||
if self.tabPlaylist.widget(tab).playlist_id == \
|
|
||||||
src_playlist_id:
|
|
||||||
source_playlist_tab = self.tabPlaylist.widget(tab)
|
|
||||||
break
|
|
||||||
if source_playlist_tab:
|
|
||||||
source_playlist_tab.populate(session, src_playlist_id)
|
|
||||||
|
|
||||||
# Reset so rows can't be repasted
|
|
||||||
self.selected_plrs = None
|
|
||||||
|
|
||||||
def play_next(self) -> None:
|
def play_next(self) -> None:
|
||||||
"""
|
"""
|
||||||
Play next track.
|
Play next track.
|
||||||
@ -1638,13 +1557,6 @@ if __name__ == "__main__":
|
|||||||
win = Window()
|
win = Window()
|
||||||
win.show()
|
win.show()
|
||||||
sys.exit(app.exec())
|
sys.exit(app.exec())
|
||||||
except Exception as exc:
|
except Exception:
|
||||||
from helpers import send_mail
|
msg = "Unhandled Exception caught by musicmuster.main()"
|
||||||
|
log.exception(msg, exc_info=True, stack_info=True)
|
||||||
msg = stackprinter.format(exc)
|
|
||||||
send_mail(Config.ERRORS_TO, Config.ERRORS_FROM,
|
|
||||||
"Exception from musicmuster", msg)
|
|
||||||
|
|
||||||
print("\033[1;31;47mUnhandled exception starts\033[1;37;40m")
|
|
||||||
stackprinter.show(style="darkbg2")
|
|
||||||
print("\033[1;31;47mUnhandled exception ends\033[1;37;40m")
|
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import re
|
import re
|
||||||
import stackprinter
|
|
||||||
import subprocess
|
import subprocess
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
@ -46,7 +45,6 @@ from helpers import (
|
|||||||
get_relative_date,
|
get_relative_date,
|
||||||
ms_to_mmss,
|
ms_to_mmss,
|
||||||
open_in_audacity,
|
open_in_audacity,
|
||||||
send_mail,
|
|
||||||
set_track_metadata,
|
set_track_metadata,
|
||||||
)
|
)
|
||||||
from log import log
|
from log import log
|
||||||
@ -287,21 +285,6 @@ class PlaylistTab(QTableWidget):
|
|||||||
else:
|
else:
|
||||||
current = next_row = False
|
current = next_row = False
|
||||||
|
|
||||||
# Cut/paste
|
|
||||||
act_cut = self.menu.addAction(
|
|
||||||
"Mark for moving")
|
|
||||||
act_cut.triggered.connect(
|
|
||||||
lambda: self.musicmuster.cut_rows())
|
|
||||||
|
|
||||||
act_paste = self.menu.addAction(
|
|
||||||
"Paste")
|
|
||||||
act_paste.setDisabled(
|
|
||||||
self.musicmuster.selected_plrs is None)
|
|
||||||
act_paste.triggered.connect(
|
|
||||||
lambda: self.musicmuster.paste_rows())
|
|
||||||
|
|
||||||
self.menu.addSeparator()
|
|
||||||
|
|
||||||
if track_row:
|
if track_row:
|
||||||
# Info
|
# Info
|
||||||
act_info = self.menu.addAction('Info')
|
act_info = self.menu.addAction('Info')
|
||||||
@ -452,7 +435,7 @@ class PlaylistTab(QTableWidget):
|
|||||||
update_current = row == self._get_current_track_row()
|
update_current = row == self._get_current_track_row()
|
||||||
update_next = row == self._get_next_track_row()
|
update_next = row == self._get_next_track_row()
|
||||||
if self.edit_cell_type == TITLE:
|
if self.edit_cell_type == TITLE:
|
||||||
log.debug(f"KAE: _cell_changed:440, {new_text=}")
|
log.debug(f"KAE: _cell_changed:438, {new_text=}")
|
||||||
track.title = new_text
|
track.title = new_text
|
||||||
elif self.edit_cell_type == ARTIST:
|
elif self.edit_cell_type == ARTIST:
|
||||||
track.artist = new_text
|
track.artist = new_text
|
||||||
@ -631,7 +614,7 @@ class PlaylistTab(QTableWidget):
|
|||||||
self.setItem(row, START_GAP, start_gap_item)
|
self.setItem(row, START_GAP, start_gap_item)
|
||||||
|
|
||||||
title_item = QTableWidgetItem(row_data.track.title)
|
title_item = QTableWidgetItem(row_data.track.title)
|
||||||
log.debug(f"KAE: insert_row:619, {title_item.text()=}")
|
log.debug(f"KAE: insert_row:615, {title_item.text()=}")
|
||||||
self.setItem(row, TITLE, title_item)
|
self.setItem(row, TITLE, title_item)
|
||||||
|
|
||||||
artist_item = QTableWidgetItem(row_data.track.artist)
|
artist_item = QTableWidgetItem(row_data.track.artist)
|
||||||
@ -841,12 +824,14 @@ class PlaylistTab(QTableWidget):
|
|||||||
"""Scroll currently-playing row to top"""
|
"""Scroll currently-playing row to top"""
|
||||||
|
|
||||||
current_row = self._get_current_track_row()
|
current_row = self._get_current_track_row()
|
||||||
|
log.debug(f"KAE: playlists.scroll_current_to_top(), {current_row=}")
|
||||||
self._scroll_to_top(current_row)
|
self._scroll_to_top(current_row)
|
||||||
|
|
||||||
def scroll_next_to_top(self) -> None:
|
def scroll_next_to_top(self) -> None:
|
||||||
"""Scroll nextly-playing row to top"""
|
"""Scroll nextly-playing row to top"""
|
||||||
|
|
||||||
next_row = self._get_next_track_row()
|
next_row = self._get_next_track_row()
|
||||||
|
log.debug(f"KAE: playlists.scroll_next_to_top(), {next_row=}")
|
||||||
self._scroll_to_top(next_row)
|
self._scroll_to_top(next_row)
|
||||||
|
|
||||||
def set_search(self, text: str) -> None:
|
def set_search(self, text: str) -> None:
|
||||||
@ -1530,13 +1515,11 @@ class PlaylistTab(QTableWidget):
|
|||||||
return self.selectionModel().selectedRows()[0].row()
|
return self.selectionModel().selectedRows()[0].row()
|
||||||
|
|
||||||
def _get_selected_rows(self) -> List[int]:
|
def _get_selected_rows(self) -> List[int]:
|
||||||
"""Return a list of selected row numbers sorted by row"""
|
"""Return a list of selected row numbers"""
|
||||||
|
|
||||||
# Use a set to deduplicate result (a selected row will have all
|
# Use a set to deduplicate result (a selected row will have all
|
||||||
# items in that row selected)
|
# items in that row selected)
|
||||||
return sorted(
|
return [row for row in set([a.row() for a in self.selectedItems()])]
|
||||||
[row for row in set([a.row() for a in self.selectedItems()])]
|
|
||||||
)
|
|
||||||
|
|
||||||
def _get_unreadable_track_rows(self) -> List[int]:
|
def _get_unreadable_track_rows(self) -> List[int]:
|
||||||
"""Return rows marked as unreadable, or None"""
|
"""Return rows marked as unreadable, or None"""
|
||||||
@ -1970,12 +1953,8 @@ class PlaylistTab(QTableWidget):
|
|||||||
# FIXME temporary workaround to issue #147
|
# FIXME temporary workaround to issue #147
|
||||||
try:
|
try:
|
||||||
self.item(playlist_row.row_number, column).setText(new_text)
|
self.item(playlist_row.row_number, column).setText(new_text)
|
||||||
except AttributeError as exc:
|
except AttributeError:
|
||||||
msg = f"Issue 147 occurred. {playlist_row=}, {additional_text=}"
|
pass
|
||||||
msg += "\n\n"
|
|
||||||
msg += stackprinter.format(exc)
|
|
||||||
helpers.send_mail(Config.ERRORS_TO, Confit.ERRORS_FROM,
|
|
||||||
"Issue #147 from musicmuster", msg)
|
|
||||||
|
|
||||||
def _update_row(self, session, row: int, track: Tracks) -> None:
|
def _update_row(self, session, row: int, track: Tracks) -> None:
|
||||||
"""
|
"""
|
||||||
@ -1990,7 +1969,7 @@ class PlaylistTab(QTableWidget):
|
|||||||
item_startgap.setBackground(QColor("white"))
|
item_startgap.setBackground(QColor("white"))
|
||||||
|
|
||||||
item_title = self.item(row, TITLE)
|
item_title = self.item(row, TITLE)
|
||||||
log.debug(f"KAE: _update_row:1978, {track.title=}")
|
log.debug(f"KAE: _update_row:1958, {track.title=}")
|
||||||
item_title.setText(track.title)
|
item_title.setText(track.title)
|
||||||
|
|
||||||
item_artist = self.item(row, ARTIST)
|
item_artist = self.item(row, ARTIST)
|
||||||
|
|||||||
@ -866,9 +866,6 @@ padding-left: 8px;</string>
|
|||||||
<addaction name="action_Clear_selection"/>
|
<addaction name="action_Clear_selection"/>
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
<addaction name="actionEnable_controls"/>
|
<addaction name="actionEnable_controls"/>
|
||||||
<addaction name="separator"/>
|
|
||||||
<addaction name="actionMark_for_moving"/>
|
|
||||||
<addaction name="actionPaste"/>
|
|
||||||
</widget>
|
</widget>
|
||||||
<widget class="QMenu" name="menuSearc_h">
|
<widget class="QMenu" name="menuSearc_h">
|
||||||
<property name="title">
|
<property name="title">
|
||||||
@ -1181,22 +1178,6 @@ padding-left: 8px;</string>
|
|||||||
<string>Edit cart &1...</string>
|
<string>Edit cart &1...</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
<action name="actionMark_for_moving">
|
|
||||||
<property name="text">
|
|
||||||
<string>Mark for moving</string>
|
|
||||||
</property>
|
|
||||||
<property name="shortcut">
|
|
||||||
<string>Ctrl+C</string>
|
|
||||||
</property>
|
|
||||||
</action>
|
|
||||||
<action name="actionPaste">
|
|
||||||
<property name="text">
|
|
||||||
<string>Paste</string>
|
|
||||||
</property>
|
|
||||||
<property name="shortcut">
|
|
||||||
<string>Ctrl+V</string>
|
|
||||||
</property>
|
|
||||||
</action>
|
|
||||||
</widget>
|
</widget>
|
||||||
<customwidgets>
|
<customwidgets>
|
||||||
<customwidget>
|
<customwidget>
|
||||||
|
|||||||
@ -499,10 +499,6 @@ class Ui_MainWindow(object):
|
|||||||
self.actionDebug.setObjectName("actionDebug")
|
self.actionDebug.setObjectName("actionDebug")
|
||||||
self.actionAdd_cart = QtWidgets.QAction(MainWindow)
|
self.actionAdd_cart = QtWidgets.QAction(MainWindow)
|
||||||
self.actionAdd_cart.setObjectName("actionAdd_cart")
|
self.actionAdd_cart.setObjectName("actionAdd_cart")
|
||||||
self.actionMark_for_moving = QtWidgets.QAction(MainWindow)
|
|
||||||
self.actionMark_for_moving.setObjectName("actionMark_for_moving")
|
|
||||||
self.actionPaste = QtWidgets.QAction(MainWindow)
|
|
||||||
self.actionPaste.setObjectName("actionPaste")
|
|
||||||
self.menuFile.addAction(self.actionNewPlaylist)
|
self.menuFile.addAction(self.actionNewPlaylist)
|
||||||
self.menuFile.addAction(self.actionOpenPlaylist)
|
self.menuFile.addAction(self.actionOpenPlaylist)
|
||||||
self.menuFile.addAction(self.actionClosePlaylist)
|
self.menuFile.addAction(self.actionClosePlaylist)
|
||||||
@ -534,9 +530,6 @@ class Ui_MainWindow(object):
|
|||||||
self.menuPlaylist.addAction(self.action_Clear_selection)
|
self.menuPlaylist.addAction(self.action_Clear_selection)
|
||||||
self.menuPlaylist.addSeparator()
|
self.menuPlaylist.addSeparator()
|
||||||
self.menuPlaylist.addAction(self.actionEnable_controls)
|
self.menuPlaylist.addAction(self.actionEnable_controls)
|
||||||
self.menuPlaylist.addSeparator()
|
|
||||||
self.menuPlaylist.addAction(self.actionMark_for_moving)
|
|
||||||
self.menuPlaylist.addAction(self.actionPaste)
|
|
||||||
self.menuSearc_h.addAction(self.actionSearch)
|
self.menuSearc_h.addAction(self.actionSearch)
|
||||||
self.menuSearc_h.addAction(self.actionFind_next)
|
self.menuSearc_h.addAction(self.actionFind_next)
|
||||||
self.menuSearc_h.addAction(self.actionFind_previous)
|
self.menuSearc_h.addAction(self.actionFind_previous)
|
||||||
@ -642,9 +635,5 @@ class Ui_MainWindow(object):
|
|||||||
self.actionNew_from_template.setText(_translate("MainWindow", "New from template..."))
|
self.actionNew_from_template.setText(_translate("MainWindow", "New from template..."))
|
||||||
self.actionDebug.setText(_translate("MainWindow", "Debug"))
|
self.actionDebug.setText(_translate("MainWindow", "Debug"))
|
||||||
self.actionAdd_cart.setText(_translate("MainWindow", "Edit cart &1..."))
|
self.actionAdd_cart.setText(_translate("MainWindow", "Edit cart &1..."))
|
||||||
self.actionMark_for_moving.setText(_translate("MainWindow", "Mark for moving"))
|
|
||||||
self.actionMark_for_moving.setShortcut(_translate("MainWindow", "Ctrl+C"))
|
|
||||||
self.actionPaste.setText(_translate("MainWindow", "Paste"))
|
|
||||||
self.actionPaste.setShortcut(_translate("MainWindow", "Ctrl+V"))
|
|
||||||
from infotabs import InfoTabs
|
from infotabs import InfoTabs
|
||||||
import icons_rc
|
import icons_rc
|
||||||
|
|||||||
11
poetry.lock
generated
11
poetry.lock
generated
@ -624,14 +624,6 @@ pure-eval = "*"
|
|||||||
[package.extras]
|
[package.extras]
|
||||||
tests = ["pytest", "typeguard", "pygments", "littleutils", "cython"]
|
tests = ["pytest", "typeguard", "pygments", "littleutils", "cython"]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "stackprinter"
|
|
||||||
version = "0.2.10"
|
|
||||||
description = "Debug-friendly stack traces, with variable values and semantic highlighting"
|
|
||||||
category = "main"
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.4"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "text-unidecode"
|
name = "text-unidecode"
|
||||||
version = "1.3"
|
version = "1.3"
|
||||||
@ -716,7 +708,7 @@ python-versions = "*"
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "1.1"
|
lock-version = "1.1"
|
||||||
python-versions = "^3.9"
|
python-versions = "^3.9"
|
||||||
content-hash = "0fdda77377246e18b5e85459fa2c26173f14467f32e71c576b30fa0899ced8b0"
|
content-hash = "91e055875df86707e1ce1544b1d29126265011d750897912daa37af3fe005498"
|
||||||
|
|
||||||
[metadata.files]
|
[metadata.files]
|
||||||
alembic = [
|
alembic = [
|
||||||
@ -1081,7 +1073,6 @@ stack-data = [
|
|||||||
{file = "stack_data-0.2.0-py3-none-any.whl", hash = "sha256:999762f9c3132308789affa03e9271bbbe947bf78311851f4d485d8402ed858e"},
|
{file = "stack_data-0.2.0-py3-none-any.whl", hash = "sha256:999762f9c3132308789affa03e9271bbbe947bf78311851f4d485d8402ed858e"},
|
||||||
{file = "stack_data-0.2.0.tar.gz", hash = "sha256:45692d41bd633a9503a5195552df22b583caf16f0b27c4e58c98d88c8b648e12"},
|
{file = "stack_data-0.2.0.tar.gz", hash = "sha256:45692d41bd633a9503a5195552df22b583caf16f0b27c4e58c98d88c8b648e12"},
|
||||||
]
|
]
|
||||||
stackprinter = []
|
|
||||||
text-unidecode = []
|
text-unidecode = []
|
||||||
thefuzz = []
|
thefuzz = []
|
||||||
tinytag = [
|
tinytag = [
|
||||||
|
|||||||
@ -23,7 +23,6 @@ thefuzz = "^0.19.0"
|
|||||||
python-Levenshtein = "^0.12.2"
|
python-Levenshtein = "^0.12.2"
|
||||||
pyfzf = "^0.3.1"
|
pyfzf = "^0.3.1"
|
||||||
pydymenu = "^0.5.2"
|
pydymenu = "^0.5.2"
|
||||||
stackprinter = "^0.2.10"
|
|
||||||
|
|
||||||
[tool.poetry.dev-dependencies]
|
[tool.poetry.dev-dependencies]
|
||||||
ipdb = "^0.13.9"
|
ipdb = "^0.13.9"
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user