Compare commits
No commits in common. "f182f49f15df2a8dbdb85f192e8846e2b44db1c3" and "ffa3015ac389427123d7750d8f515cd3965f5e9e" have entirely different histories.
f182f49f15
...
ffa3015ac3
@ -5,17 +5,21 @@ import smtplib
|
||||
import ssl
|
||||
import tempfile
|
||||
|
||||
from config import Config
|
||||
from datetime import datetime
|
||||
from email.message import EmailMessage
|
||||
from log import log
|
||||
from mutagen.flac import FLAC # type: ignore
|
||||
from mutagen.mp3 import MP3 # type: ignore
|
||||
from pydub import AudioSegment, effects
|
||||
from pydub import effects
|
||||
from pydub.utils import mediainfo
|
||||
|
||||
from config import Config
|
||||
from datetime import datetime
|
||||
from log import log
|
||||
from pydub import AudioSegment
|
||||
from PyQt5.QtWidgets import QMessageBox
|
||||
from tinytag import TinyTag # type: ignore
|
||||
from typing import Any, Dict, Optional, Union
|
||||
from typing import Optional
|
||||
# from typing import Dict, Optional, Union
|
||||
from typing import Dict, Union
|
||||
|
||||
|
||||
def ask_yes_no(title: str, question: str) -> bool:
|
||||
@ -78,7 +82,7 @@ def get_audio_segment(path: str) -> Optional[AudioSegment]:
|
||||
return None
|
||||
|
||||
|
||||
def get_tags(path: str) -> Dict[str, Any]:
|
||||
def get_tags(path: str) -> Dict[str, Union[str, int]]:
|
||||
"""
|
||||
Return a dictionary of title, artist, duration-in-milliseconds and path.
|
||||
"""
|
||||
@ -94,7 +98,7 @@ def get_tags(path: str) -> Dict[str, Any]:
|
||||
)
|
||||
|
||||
|
||||
def get_relative_date(past_date: Optional[datetime],
|
||||
def get_relative_date(past_date: datetime,
|
||||
reference_date: Optional[datetime] = None) -> str:
|
||||
"""
|
||||
Return how long before reference_date past_date is as string.
|
||||
@ -185,8 +189,7 @@ def send_mail(to_addr, from_addr, subj, body):
|
||||
s.quit()
|
||||
|
||||
|
||||
def ms_to_mmss(ms: Optional[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"""
|
||||
|
||||
minutes: int
|
||||
|
||||
@ -63,7 +63,7 @@ class Carts(Base):
|
||||
|
||||
def __init__(self, session: scoped_session, cart_number: int,
|
||||
name: Optional[str] = None,
|
||||
duration: Optional[int] = None, path: Optional[str] = None,
|
||||
duration: int = None, path: str = None,
|
||||
enabled: bool = True) -> None:
|
||||
"""Create new cart"""
|
||||
|
||||
@ -132,7 +132,7 @@ class Playdates(Base):
|
||||
id: int = Column(Integer, primary_key=True, autoincrement=True)
|
||||
lastplayed = Column(DateTime, index=True, default=None)
|
||||
track_id = Column(Integer, ForeignKey('tracks.id'))
|
||||
track: "Tracks" = relationship("Tracks", back_populates="playdates")
|
||||
track = relationship("Tracks", back_populates="playdates")
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
@ -149,8 +149,7 @@ class Playdates(Base):
|
||||
session.commit()
|
||||
|
||||
@staticmethod
|
||||
def last_played(session: scoped_session,
|
||||
track_id: int) -> Optional[datetime]:
|
||||
def last_played(session: scoped_session, track_id: int) -> Optional[datetime]:
|
||||
"""Return datetime track last played or None"""
|
||||
|
||||
last_played = session.execute(
|
||||
@ -166,8 +165,7 @@ class Playdates(Base):
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def played_after(session: scoped_session,
|
||||
since: datetime) -> List["Playdates"]:
|
||||
def played_after(session: scoped_session, since: datetime) -> List["Playdates"]:
|
||||
"""Return a list of Playdates objects since passed time"""
|
||||
|
||||
return (
|
||||
@ -188,7 +186,7 @@ class Playlists(Base):
|
||||
|
||||
__tablename__ = "playlists"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True, nullable=False)
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
name = Column(String(32), nullable=False, unique=True)
|
||||
last_used = Column(DateTime, default=None, nullable=True)
|
||||
tab = Column(Integer, default=None, nullable=True, unique=True)
|
||||
@ -196,7 +194,7 @@ class Playlists(Base):
|
||||
is_template = Column(Boolean, default=False, nullable=False)
|
||||
query = Column(String(256), default=None, nullable=True, unique=False)
|
||||
deleted = Column(Boolean, default=False, nullable=False)
|
||||
rows: "PlaylistRows" = relationship(
|
||||
rows = relationship(
|
||||
"PlaylistRows",
|
||||
back_populates="playlist",
|
||||
cascade="all, delete-orphan",
|
||||
@ -209,10 +207,10 @@ class Playlists(Base):
|
||||
f"is_templatee={self.is_template}>"
|
||||
)
|
||||
|
||||
def __init__(self, session: scoped_session, name: str):
|
||||
def __init__(self, session: scoped_session, name: str) -> None:
|
||||
self.name = name
|
||||
session.add(self)
|
||||
session.flush()
|
||||
session.commit()
|
||||
|
||||
def close(self, session: scoped_session) -> None:
|
||||
"""Mark playlist as unloaded"""
|
||||
@ -233,15 +231,10 @@ class Playlists(Base):
|
||||
session: scoped_session,
|
||||
template: "Playlists",
|
||||
playlist_name: str) \
|
||||
-> Optional["Playlists"]:
|
||||
-> "Playlists":
|
||||
"""Create a new playlist from template"""
|
||||
|
||||
playlist = cls(session, playlist_name)
|
||||
|
||||
# Sanity / mypy checks
|
||||
if not playlist or not playlist.id or not template.id:
|
||||
return None
|
||||
|
||||
PlaylistRows.copy_playlist(session, template.id, playlist.id)
|
||||
|
||||
return playlist
|
||||
@ -339,9 +332,6 @@ class Playlists(Base):
|
||||
"""Save passed playlist as new template"""
|
||||
|
||||
template = Playlists(session, template_name)
|
||||
if not template or not template.id:
|
||||
return
|
||||
|
||||
template.is_template = True
|
||||
session.commit()
|
||||
|
||||
@ -355,9 +345,9 @@ class PlaylistRows(Base):
|
||||
row_number = Column(Integer, nullable=False)
|
||||
note = Column(String(2048), index=False)
|
||||
playlist_id = Column(Integer, ForeignKey('playlists.id'), nullable=False)
|
||||
playlist: Playlists = relationship(Playlists, back_populates="rows")
|
||||
playlist = relationship(Playlists, back_populates="rows")
|
||||
track_id = Column(Integer, ForeignKey('tracks.id'), nullable=True)
|
||||
track: "Tracks" = relationship("Tracks", back_populates="playlistrows")
|
||||
track = relationship("Tracks", back_populates="playlistrows")
|
||||
played = Column(Boolean, nullable=False, index=False, default=False)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
@ -458,8 +448,7 @@ class PlaylistRows(Base):
|
||||
).first()
|
||||
|
||||
@staticmethod
|
||||
def get_last_used_row(session: scoped_session,
|
||||
playlist_id: int) -> Optional[int]:
|
||||
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(
|
||||
@ -526,8 +515,8 @@ class PlaylistRows(Base):
|
||||
return plrs
|
||||
|
||||
@staticmethod
|
||||
def move_rows_down(session: scoped_session, playlist_id: int,
|
||||
starting_row: int, move_by: int) -> None:
|
||||
def move_rows_down(session: scoped_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
|
||||
@ -578,27 +567,27 @@ class Settings(Base):
|
||||
value = self.f_datetime or self.f_int or self.f_string
|
||||
return f"<Settings(id={self.id}, name={self.name}, {value=}>"
|
||||
|
||||
def __init__(self, session: scoped_session, name: str):
|
||||
|
||||
self.name = name
|
||||
session.add(self)
|
||||
session.flush()
|
||||
|
||||
@classmethod
|
||||
def get_int_settings(cls, session: scoped_session,
|
||||
name: str) -> "Settings":
|
||||
def get_int_settings(cls, session: scoped_session, name: str) -> "Settings":
|
||||
"""Get setting for an integer or return new setting record"""
|
||||
|
||||
int_setting: Settings
|
||||
|
||||
try:
|
||||
return session.execute(
|
||||
int_setting = session.execute(
|
||||
select(cls)
|
||||
.where(cls.name == name)
|
||||
).scalar_one()
|
||||
|
||||
except NoResultFound:
|
||||
return Settings(session, name)
|
||||
int_setting = Settings()
|
||||
int_setting.name = name
|
||||
int_setting.f_int = None
|
||||
session.add(int_setting)
|
||||
|
||||
def update(self, session: scoped_session, data: dict):
|
||||
return int_setting
|
||||
|
||||
def update(self, session: scoped_session, data: "Settings"):
|
||||
for key, value in data.items():
|
||||
assert hasattr(self, key)
|
||||
setattr(self, key, value)
|
||||
@ -618,10 +607,9 @@ class Tracks(Base):
|
||||
path = Column(String(2048), index=False, nullable=False, unique=True)
|
||||
mtime = Column(Float, index=True)
|
||||
bitrate = Column(Integer, nullable=True, default=None)
|
||||
playlistrows: PlaylistRows = relationship("PlaylistRows",
|
||||
back_populates="track")
|
||||
playlistrows = relationship("PlaylistRows", back_populates="track")
|
||||
playlists = association_proxy("playlistrows", "playlist")
|
||||
playdates: Playdates = relationship("Playdates", back_populates="track")
|
||||
playdates = relationship("Playdates", back_populates="track")
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
@ -662,8 +650,7 @@ class Tracks(Base):
|
||||
return session.execute(select(cls)).scalars().all()
|
||||
|
||||
@classmethod
|
||||
def get_by_path(cls, session: scoped_session,
|
||||
path: str) -> Optional["Tracks"]:
|
||||
def get_by_path(cls, session: scoped_session, path: str) -> "Tracks":
|
||||
"""
|
||||
Return track with passed path, or None.
|
||||
"""
|
||||
@ -679,8 +666,7 @@ class Tracks(Base):
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def search_artists(cls, session: scoped_session,
|
||||
text: str) -> List["Tracks"]:
|
||||
def search_artists(cls, session: scoped_session, text: str) -> List["Tracks"]:
|
||||
"""Search case-insenstively for artists containing str"""
|
||||
|
||||
return (
|
||||
@ -694,8 +680,7 @@ class Tracks(Base):
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def search_titles(cls, session: scoped_session,
|
||||
text: str) -> List["Tracks"]:
|
||||
def search_titles(cls, session: scoped_session, text: str) -> List["Tracks"]:
|
||||
"""Search case-insenstively for titles containing str"""
|
||||
return (
|
||||
session.execute(
|
||||
|
||||
@ -27,7 +27,7 @@ from PyQt5.QtWidgets import (
|
||||
QProgressBar,
|
||||
)
|
||||
|
||||
from dbconfig import engine, Session, scoped_session
|
||||
from dbconfig import engine, Session
|
||||
import helpers
|
||||
import music
|
||||
|
||||
@ -59,8 +59,7 @@ class CartButton(QPushButton):
|
||||
"""Create a cart pushbutton and set it disabled"""
|
||||
|
||||
super().__init__(parent)
|
||||
# Next line is redundant (check)
|
||||
# self.parent = parent
|
||||
self.parent = parent
|
||||
self.cart_id = cart.id
|
||||
if cart.path and cart.enabled and not cart.duration:
|
||||
tags = helpers.get_tags(cart.path)
|
||||
@ -127,28 +126,28 @@ class PlaylistTrack:
|
||||
number: that's the playlist's problem.
|
||||
"""
|
||||
|
||||
self.artist: Optional[str] = None
|
||||
self.duration: Optional[int] = None
|
||||
self.end_time: Optional[datetime] = None
|
||||
self.fade_at: Optional[int] = None
|
||||
self.fade_length: Optional[int] = None
|
||||
self.path: Optional[str] = None
|
||||
self.playlist_id: Optional[int] = None
|
||||
self.playlist_tab: Optional[PlaylistTab] = None
|
||||
self.plr_id: Optional[int] = None
|
||||
self.silence_at: Optional[datetime] = None
|
||||
self.start_gap: Optional[int] = None
|
||||
self.start_time: Optional[datetime] = None
|
||||
self.title: Optional[str] = None
|
||||
self.track_id: Optional[int] = None
|
||||
self.artist = None
|
||||
self.duration = None
|
||||
self.end_time = None
|
||||
self.fade_at = None
|
||||
self.fade_length = None
|
||||
self.path = None
|
||||
self.playlist_id = None
|
||||
self.playlist_tab = None
|
||||
self.plr_id = None
|
||||
self.silence_at = None
|
||||
self.start_gap = None
|
||||
self.start_time = None
|
||||
self.title = None
|
||||
self.track_id = None
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
f"<PlaylistTrack(title={self.title}, artist={self.artist}, "
|
||||
f"playlist_id={self.playlist_id}>"
|
||||
f"row_number={self.row_number} playlist_id={self.playlist_id}>"
|
||||
)
|
||||
|
||||
def set_plr(self, session: scoped_session, plr: PlaylistRows,
|
||||
def set_plr(self, session: Session, plr: PlaylistRows,
|
||||
tab: PlaylistTab) -> None:
|
||||
"""
|
||||
Update with new plr information
|
||||
@ -493,7 +492,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
self.timer.timeout.connect(self.tick)
|
||||
|
||||
def create_playlist(self,
|
||||
session: scoped_session,
|
||||
session: Session,
|
||||
playlist_name: Optional[str] = None) -> Playlists:
|
||||
"""Create new playlist"""
|
||||
|
||||
@ -513,7 +512,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
if playlist:
|
||||
self.create_playlist_tab(session, playlist)
|
||||
|
||||
def create_playlist_tab(self, session: scoped_session,
|
||||
def create_playlist_tab(self, session: Session,
|
||||
playlist: Playlists) -> int:
|
||||
"""
|
||||
Take the passed playlist database object, create a playlist tab and
|
||||
@ -715,7 +714,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
QMessageBox.Ok
|
||||
)
|
||||
|
||||
def get_one_track(self, session: scoped_session) -> Optional[Tracks]:
|
||||
def get_one_track(self, session: Session) -> Optional[Tracks]:
|
||||
"""Show dialog box to select one track and return it to caller"""
|
||||
|
||||
dlg = DbDialog(self, session, get_one_track=True)
|
||||
@ -834,10 +833,10 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
_ = self.create_playlist_tab(session, playlist)
|
||||
# Set active tab
|
||||
record = Settings.get_int_settings(session, "active_tab")
|
||||
if record and record.f_int >= 0:
|
||||
if record and record.f_int is not None:
|
||||
self.tabPlaylist.setCurrentIndex(record.f_int)
|
||||
|
||||
def move_playlist_rows(self, session: scoped_session,
|
||||
def move_playlist_rows(self, session: Session,
|
||||
playlistrows: List[PlaylistRows]) -> None:
|
||||
"""
|
||||
Move passed playlist rows to another playlist
|
||||
@ -1326,7 +1325,7 @@ 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,
|
||||
def this_is_the_next_playlist_row(self, session: Session,
|
||||
plr: PlaylistRows,
|
||||
playlist_tab: PlaylistTab) -> None:
|
||||
"""
|
||||
@ -1469,9 +1468,6 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
# Time to end
|
||||
self.label_end_timer.setText(helpers.ms_to_mmss(time_to_end))
|
||||
|
||||
# Autoplay next track
|
||||
# if time_to_silence <= 1500:
|
||||
# self.play_next()
|
||||
else:
|
||||
if self.playing:
|
||||
self.stop_playing()
|
||||
@ -1504,7 +1500,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
||||
class CartDialog(QDialog):
|
||||
"""Edit cart details"""
|
||||
|
||||
def __init__(self, parent: QMainWindow, session: scoped_session,
|
||||
def __init__(self, parent: QMainWindow, session: Session,
|
||||
cart: Carts) -> None:
|
||||
"""
|
||||
Manage carts
|
||||
@ -1541,7 +1537,7 @@ class CartDialog(QDialog):
|
||||
class DbDialog(QDialog):
|
||||
"""Select track from database"""
|
||||
|
||||
def __init__(self, parent: QMainWindow, session: scoped_session,
|
||||
def __init__(self, parent: QMainWindow, session: Session,
|
||||
get_one_track: bool = False) -> None:
|
||||
"""
|
||||
Subclassed QDialog to manage track selection
|
||||
@ -1766,12 +1762,10 @@ if __name__ == "__main__":
|
||||
log.debug("Updating database")
|
||||
with Session() as session:
|
||||
check_db(session)
|
||||
engine.dispose()
|
||||
elif args.update_bitrates:
|
||||
log.debug("Update bitrates")
|
||||
with Session() as session:
|
||||
update_bitrates(session)
|
||||
engine.dispose()
|
||||
else:
|
||||
# Normal run
|
||||
try:
|
||||
@ -1779,9 +1773,7 @@ if __name__ == "__main__":
|
||||
app = QApplication(sys.argv)
|
||||
win = Window()
|
||||
win.show()
|
||||
status = app.exec()
|
||||
engine.dispose()
|
||||
sys.exit(status)
|
||||
sys.exit(app.exec())
|
||||
except Exception as exc:
|
||||
from helpers import send_mail
|
||||
|
||||
|
||||
@ -435,6 +435,7 @@ class PlaylistTab(QTableWidget):
|
||||
track = session.get(Tracks, track_id)
|
||||
if track:
|
||||
if self.edit_cell_type == TITLE:
|
||||
log.debug(f"KAE: _cell_changed:440, {new_text=}")
|
||||
track.title = new_text
|
||||
if update_current:
|
||||
self.musicmuster.current_track.title = new_text
|
||||
@ -607,6 +608,7 @@ class PlaylistTab(QTableWidget):
|
||||
self.setItem(row, START_GAP, start_gap_item)
|
||||
|
||||
title_item = QTableWidgetItem(plr.track.title)
|
||||
log.debug(f"KAE: insert_row:619, {title_item.text()=}")
|
||||
self.setItem(row, TITLE, title_item)
|
||||
|
||||
artist_item = QTableWidgetItem(plr.track.artist)
|
||||
@ -665,8 +667,7 @@ class PlaylistTab(QTableWidget):
|
||||
userdata_item.setData(self.ROW_TRACK_ID, 0)
|
||||
|
||||
if repaint:
|
||||
# Schedule so that display can update with new row first
|
||||
QTimer.singleShot(0, lambda: self.update_display(session))
|
||||
self.update_display(session)
|
||||
|
||||
def insert_track(self, session: scoped_session, track: Tracks,
|
||||
note: Optional[str] = None, repaint: bool = True) -> None:
|
||||
@ -1205,7 +1206,7 @@ class PlaylistTab(QTableWidget):
|
||||
|
||||
return start + timedelta(milliseconds=duration)
|
||||
|
||||
def _column_resize(self, idx: int, _old: int, _new: int) -> None:
|
||||
def _column_resize(self, idx: int, old: int, new: int) -> None:
|
||||
"""
|
||||
Called when column widths are changed.
|
||||
|
||||
@ -1360,7 +1361,7 @@ class PlaylistTab(QTableWidget):
|
||||
return playlistrow_id
|
||||
|
||||
def _get_playlistrow_object(self, session: scoped_session,
|
||||
row: int) -> PlaylistRows:
|
||||
row: int) -> int:
|
||||
"""Return the playlistrow object associated with this row"""
|
||||
|
||||
playlistrow_id = (self.item(row, USERDATA).data(self.PLAYLISTROW_ID))
|
||||
@ -1489,6 +1490,48 @@ class PlaylistTab(QTableWidget):
|
||||
and pos.y() >= rect.center().y() # noqa W503
|
||||
)
|
||||
|
||||
def _meta_clear_attribute(self, row: int, attribute: int) -> None:
|
||||
"""Clear given metadata for row"""
|
||||
|
||||
if row is None:
|
||||
raise ValueError(f"_meta_clear_attribute({row=}, {attribute=})")
|
||||
|
||||
new_metadata: int = self._meta_get(row) & ~(1 << attribute)
|
||||
self.item(row, USERDATA).setData(self.ROW_FLAGS, new_metadata)
|
||||
|
||||
def _meta_get(self, row: int) -> int:
|
||||
"""Return row metadata"""
|
||||
|
||||
return (self.item(row, USERDATA).data(self.ROW_FLAGS))
|
||||
|
||||
def _meta_search(self, metadata: int, one: bool = True) -> List[int]:
|
||||
"""
|
||||
Search rows for metadata.
|
||||
|
||||
If one is True, check that only one row matches and return
|
||||
the row number.
|
||||
|
||||
If one is False, return a list of matching row numbers.
|
||||
"""
|
||||
|
||||
matches = []
|
||||
for row in range(self.rowCount()):
|
||||
if self._meta_get(row):
|
||||
if self._meta_get(row) & (1 << metadata):
|
||||
matches.append(row)
|
||||
|
||||
if not one:
|
||||
return matches
|
||||
|
||||
if len(matches) <= 1:
|
||||
return matches
|
||||
else:
|
||||
log.error(
|
||||
f"Multiple matches for metadata '{metadata}' found "
|
||||
f"in rows: {', '.join([str(x) for x in matches])}"
|
||||
)
|
||||
raise AttributeError(f"Multiple '{metadata}' metadata {matches}")
|
||||
|
||||
def _move_row(self, session: scoped_session, plr: PlaylistRows,
|
||||
new_row_number: int) -> None:
|
||||
"""Move playlist row to new_row_number using parent copy/paste"""
|
||||
@ -1722,7 +1765,7 @@ class PlaylistTab(QTableWidget):
|
||||
continue
|
||||
attr_name = f"playlist_{column_name}_col_width"
|
||||
record: Settings = Settings.get_int_settings(session, attr_name)
|
||||
if record and record.f_int >= 0:
|
||||
if record and record.f_int is not None:
|
||||
self.setColumnWidth(idx, record.f_int)
|
||||
else:
|
||||
self.setColumnWidth(idx, Config.DEFAULT_COLUMN_WIDTH)
|
||||
@ -1890,6 +1933,7 @@ class PlaylistTab(QTableWidget):
|
||||
item_startgap.setBackground(QColor("white"))
|
||||
|
||||
item_title = self.item(row, TITLE)
|
||||
log.debug(f"KAE: _update_row:1978, {track.title=}")
|
||||
item_title.setText(track.title)
|
||||
|
||||
item_artist = self.item(row, ARTIST)
|
||||
|
||||
@ -35,9 +35,9 @@ parent_dir = os.path.dirname(source_dir)
|
||||
|
||||
name_and_tags: List[str] = []
|
||||
tags_not_name: List[str] = []
|
||||
# multiple_similar: List[str] = []
|
||||
multiple_similar: List[str] = []
|
||||
no_match: List[str] = []
|
||||
# possibles: List[str] = []
|
||||
possibles: List[str] = []
|
||||
no_match: int = 0
|
||||
|
||||
|
||||
|
||||
17
app/ui_helpers.py
Normal file
17
app/ui_helpers.py
Normal file
@ -0,0 +1,17 @@
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtGui import QFontMetrics, QPainter
|
||||
from PyQt5.QtWidgets import QLabel
|
||||
|
||||
|
||||
# class ElideLabel(QLabel):
|
||||
# """
|
||||
# From https://stackoverflow.com/questions/11446478/
|
||||
# pyside-pyqt-truncate-text-in-qlabel-based-on-minimumsize
|
||||
# """
|
||||
#
|
||||
# def paintEvent(self, event):
|
||||
# painter = QPainter(self)
|
||||
# metrics = QFontMetrics(self.font())
|
||||
# elided = metrics.elidedText(self.text(), Qt.ElideRight, self.width())
|
||||
#
|
||||
# painter.drawText(self.rect(), self.alignment(), elided)
|
||||
@ -70,4 +70,11 @@ def do_command(command):
|
||||
return response
|
||||
|
||||
|
||||
def quick_test():
|
||||
"""Example list of commands."""
|
||||
do_command('Help: Command=Help')
|
||||
do_command('Help: Command="GetInfo"')
|
||||
# do_command('SetPreference: Name=GUI/Theme Value=classic Reload=1')
|
||||
|
||||
|
||||
do_command('Import2: Filename=/home/kae/git/musicmuster/archive/boot.flac')
|
||||
|
||||
94
play.py
Executable file
94
play.py
Executable file
@ -0,0 +1,94 @@
|
||||
#!/usr/bin/env python
|
||||
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy import text
|
||||
from sqlalchemy import Table, Column, Integer, String
|
||||
from sqlalchemy import ForeignKey
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy import insert
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy.orm import declarative_base
|
||||
from sqlalchemy.orm import relationship
|
||||
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
engine = create_engine("sqlite+pysqlite:///:memory:", echo=True, future=True)
|
||||
|
||||
|
||||
class User(Base):
|
||||
__tablename__ = 'user_account'
|
||||
id = Column(Integer, primary_key=True)
|
||||
name = Column(String(30))
|
||||
fullname = Column(String)
|
||||
addresses = relationship("Address", back_populates="user")
|
||||
|
||||
def __repr__(self):
|
||||
return (
|
||||
f"User(id={self.id!r}, name={self.name!r}, "
|
||||
f"fullname={self.fullname!r})"
|
||||
)
|
||||
|
||||
|
||||
class Address(Base):
|
||||
__tablename__ = 'address'
|
||||
id = Column(Integer, primary_key=True)
|
||||
email_address = Column(String, nullable=False)
|
||||
user_id = Column(Integer, ForeignKey('user_account.id'))
|
||||
user = relationship("User", back_populates="addresses")
|
||||
|
||||
def __repr__(self):
|
||||
return f"Address(id={self.id!r}, email_address={self.email_address!r})"
|
||||
|
||||
|
||||
Base.metadata.create_all(engine)
|
||||
|
||||
squidward = User(name="squidward", fullname="Squidward Tentacles")
|
||||
krabs = User(name="ehkrabs", fullname="Eugene H. Krabs")
|
||||
|
||||
session = Session(engine)
|
||||
|
||||
session.add(squidward)
|
||||
session.add(krabs)
|
||||
|
||||
session.commit()
|
||||
|
||||
u1 = User(name='pkrabs', fullname='Pearl Krabs')
|
||||
a1 = Address(email_address="pearl.krabs@gmail.com")
|
||||
u1.addresses.append(a1)
|
||||
a2 = Address(email_address="pearl@aol.com", user=u1)
|
||||
|
||||
session.add(u1)
|
||||
session.add(a1)
|
||||
session.add(a2)
|
||||
|
||||
session.commit()
|
||||
|
||||
|
||||
# with engine.connect() as conn:
|
||||
# conn.execute(text("CREATE TABLE some_table (x int, y int)"))
|
||||
# conn.execute(
|
||||
# text("INSERT INTO some_table (x, y) VALUES (:x, :y)"),
|
||||
# [{"x": 1, "y": 1}, {"x": 2, "y": 4}]
|
||||
# )
|
||||
# conn.commit()
|
||||
#
|
||||
# with engine.begin() as conn:
|
||||
# conn.execute(
|
||||
# text("INSERT INTO some_table (x, y) VALUES (:x, :y)"),
|
||||
# [{"x": 6, "y": 8}, {"x": 9, "y": 10}]
|
||||
# )
|
||||
#
|
||||
# # with engine.connect() as conn:
|
||||
# # result = conn.execute(text("SELECT x, y FROM some_table"))
|
||||
# # for row in result:
|
||||
# # print(f"x: {row.x} y: {row.y}")
|
||||
#
|
||||
#
|
||||
# stmt = text(
|
||||
# "SELECT x, y FROM some_table WHERE y > :y ORDER BY x, y").bindparams(y=6)
|
||||
#
|
||||
# with Session(engine) as session:
|
||||
# result = session.execute(stmt)
|
||||
# for row in result:
|
||||
# print(f"x: {row.x} y: {row.y}")
|
||||
30
poetry.lock
generated
30
poetry.lock
generated
@ -306,7 +306,7 @@ python-versions = ">=3.7"
|
||||
name = "mypy"
|
||||
version = "0.991"
|
||||
description = "Optional static typing for Python"
|
||||
category = "dev"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
@ -325,7 +325,7 @@ reports = ["lxml"]
|
||||
name = "mypy-extensions"
|
||||
version = "0.4.3"
|
||||
description = "Experimental type system extensions for programs checked with the mypy typechecker."
|
||||
category = "dev"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
@ -671,11 +671,23 @@ postgresql_psycopg2cffi = ["psycopg2cffi"]
|
||||
pymysql = ["pymysql (<1)", "pymysql"]
|
||||
sqlcipher = ["sqlcipher3-binary"]
|
||||
|
||||
[[package]]
|
||||
name = "sqlalchemy-stubs"
|
||||
version = "0.4"
|
||||
description = "SQLAlchemy stubs and mypy plugin"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[package.dependencies]
|
||||
mypy = ">=0.790"
|
||||
typing-extensions = ">=3.7.4"
|
||||
|
||||
[[package]]
|
||||
name = "sqlalchemy2-stubs"
|
||||
version = "0.0.2a32"
|
||||
version = "0.0.2a31"
|
||||
description = "Typing Stubs for SQLAlchemy 1.4"
|
||||
category = "dev"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
|
||||
@ -748,7 +760,7 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||
name = "tomli"
|
||||
version = "2.0.1"
|
||||
description = "A lil' TOML parser"
|
||||
category = "dev"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
@ -776,7 +788,7 @@ python-versions = "*"
|
||||
name = "typing-extensions"
|
||||
version = "4.4.0"
|
||||
description = "Backported and Experimental Type Hints for Python 3.7+"
|
||||
category = "dev"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.7"
|
||||
|
||||
@ -804,7 +816,7 @@ python-versions = "*"
|
||||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = "^3.9"
|
||||
content-hash = "389d73715056202a39f0efe23941943d7a6849915607cb8ceb4c77bde7e4f709"
|
||||
content-hash = "9ae8194c057d5eb51e7f65f5328f94ea85e0a48a5fe12d38ad72f4a1aae7cbca"
|
||||
|
||||
[metadata.files]
|
||||
alembic = []
|
||||
@ -910,6 +922,10 @@ six = [
|
||||
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
|
||||
]
|
||||
sqlalchemy = []
|
||||
sqlalchemy-stubs = [
|
||||
{file = "sqlalchemy-stubs-0.4.tar.gz", hash = "sha256:c665d6dd4482ef642f01027fa06c3d5e91befabb219dc71fc2a09e7d7695f7ae"},
|
||||
{file = "sqlalchemy_stubs-0.4-py3-none-any.whl", hash = "sha256:5eec7aa110adf9b957b631799a72fef396b23ff99fe296df726645d01e312aa5"},
|
||||
]
|
||||
sqlalchemy2-stubs = []
|
||||
stack-data = []
|
||||
stackprinter = []
|
||||
|
||||
@ -24,17 +24,18 @@ python-Levenshtein = "^0.12.2"
|
||||
pyfzf = "^0.3.1"
|
||||
pydymenu = "^0.5.2"
|
||||
stackprinter = "^0.2.10"
|
||||
sqlalchemy-stubs = "^0.4"
|
||||
sqlalchemy2-stubs = "^0.0.2-alpha.31"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
ipdb = "^0.13.9"
|
||||
sqlalchemy-stubs = "^0.4"
|
||||
PyQt5-stubs = "^5.15.2"
|
||||
pytest = "^7.0.1"
|
||||
pytest-qt = "^4.0.2"
|
||||
pydub-stubs = "^0.25.1"
|
||||
line-profiler = "^4.0.2"
|
||||
flakehell = "^0.9.0"
|
||||
sqlalchemy2-stubs = "^0.0.2-alpha.32"
|
||||
mypy = "^0.991"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=1.0.0"]
|
||||
@ -44,7 +45,3 @@ build-backend = "poetry.core.masonry.api"
|
||||
mypy_path = "/home/kae/.cache/pypoetry/virtualenvs/musicmuster-oWgGw1IG-py3.9:/home/kae/git/musicmuster/app"
|
||||
plugins = "sqlalchemy.ext.mypy.plugin"
|
||||
|
||||
[tool.vulture]
|
||||
exclude = ["migrations", "app/ui", "archive"]
|
||||
paths = ["app"]
|
||||
make_whitelist = true
|
||||
|
||||
16
test.py
16
test.py
@ -2,28 +2,28 @@
|
||||
|
||||
from PyQt5 import QtGui, QtWidgets
|
||||
|
||||
class TabBar(QtWidgets.QTabBar):
|
||||
class TabBar(QtWidgets.QTabWidget):
|
||||
def paintEvent(self, event):
|
||||
painter = QtWidgets.QStylePainter(self)
|
||||
option = QtWidgets.QStyleOptionTab()
|
||||
painter = QtGui.QStylePainter(self)
|
||||
option = QtGui.QStyleOptionTab()
|
||||
for index in range(self.count()):
|
||||
self.initStyleOption(option, index)
|
||||
bgcolor = QtGui.QColor(self.tabText(index))
|
||||
option.palette.setColor(QtGui.QPalette.Window, bgcolor)
|
||||
painter.drawControl(QtWidgets.QStyle.CE_TabBarTabShape, option)
|
||||
painter.drawControl(QtWidgets.QStyle.CE_TabBarTabLabel, option)
|
||||
painter.drawControl(QtGui.QStyle.CE_TabBarTabShape, option)
|
||||
painter.drawControl(QtGui.QStyle.CE_TabBarTabLabel, option)
|
||||
|
||||
class Window(QtWidgets.QTabWidget):
|
||||
def __init__(self):
|
||||
QtWidgets.QTabWidget.__init__(self)
|
||||
QtGui.QTabWidget.__init__(self)
|
||||
self.setTabBar(TabBar(self))
|
||||
for color in 'tomato orange yellow lightgreen skyblue plum'.split():
|
||||
self.addTab(QtWidgets.QWidget(self), color)
|
||||
self.addTab(QtGui.QWidget(self), color)
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
import sys
|
||||
app = QtWidgets.QApplication(sys.argv)
|
||||
app = QtGui.QApplication(sys.argv)
|
||||
window = Window()
|
||||
window.resize(420, 200)
|
||||
window.show()
|
||||
|
||||
@ -5,6 +5,7 @@ from app.models import (
|
||||
Notes,
|
||||
Playdates,
|
||||
Playlists,
|
||||
PlaylistTracks,
|
||||
Tracks,
|
||||
)
|
||||
|
||||
@ -204,11 +205,11 @@ def test_playlist_notes(session):
|
||||
# We need two notes
|
||||
note1_text = "note1 text"
|
||||
note1_row = 11
|
||||
_ = Notes(session, playlist.id, note1_row, note1_text)
|
||||
note1 = Notes(session, playlist.id, note1_row, note1_text)
|
||||
|
||||
note2_text = "note2 text"
|
||||
note2_row = 19
|
||||
_ = Notes(session, playlist.id, note2_row, note2_text)
|
||||
note2 = Notes(session, playlist.id, note2_row, note2_text)
|
||||
|
||||
notes = playlist.notes
|
||||
assert note1_text in [n.note for n in notes]
|
||||
|
||||
Loading…
Reference in New Issue
Block a user