Fix move tracks

This commit is contained in:
Keith Edmunds 2022-03-02 20:37:27 +00:00
parent f1aba41921
commit f22f2780a3
7 changed files with 257 additions and 275 deletions

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectRootManager" version="2" project-jdk-name="Poetry (musicmuster) (2)" project-jdk-type="Python SDK" />
<component name="ProjectRootManager" version="2" project-jdk-name="Poetry (musicmuster)" project-jdk-type="Python SDK" />
<component name="PythonCompatibilityInspectionAdvertiser">
<option name="version" value="3" />
</component>

View File

@ -4,7 +4,7 @@
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/app" isTestSource="false" />
</content>
<orderEntry type="jdk" jdkName="Poetry (musicmuster) (2)" jdkType="Python SDK" />
<orderEntry type="jdk" jdkName="Poetry (musicmuster)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="PyDocumentationSettings">

View File

@ -338,9 +338,6 @@ class Playlists(Base):
self.loaded = True
self.last_used = datetime.now()
if self not in session:
session.add(self)
session.commit()
def remove_all_tracks(self, session: Session) -> None:
"""

View File

@ -88,7 +88,6 @@ class Window(QMainWindow, Ui_MainWindow):
self.visible_playlist_tab().insert_track(session, track)
def set_main_window_size(self) -> None:
# TODO: V2 check
"""Set size of window from database"""
with Session() as session:
@ -355,7 +354,6 @@ class Window(QMainWindow, Ui_MainWindow):
self.end_of_track_actions()
def insert_note(self):
#TODO: V2 check
"Add non-track row to playlist"
dlg = QInputDialog(self)
@ -374,6 +372,7 @@ class Window(QMainWindow, Ui_MainWindow):
with Session() as session:
for playlist in Playlists.get_open(session):
self.create_playlist_tab(session, playlist)
playlist.mark_open(session)
def create_playlist_tab(self, session: Session,
playlist: Playlists) -> None:
@ -383,7 +382,7 @@ class Window(QMainWindow, Ui_MainWindow):
"""
playlist_tab: PlaylistTab = PlaylistTab(
parent=self, session=session, playlist=playlist)
musicmuster=self, session=session, playlist_id=playlist.id)
idx: int = self.tabPlaylist.addTab(playlist_tab, playlist.name)
self.tabPlaylist.setCurrentIndex(idx)
@ -391,47 +390,37 @@ class Window(QMainWindow, Ui_MainWindow):
"""Move selected rows to another playlist"""
with Session() as session:
visible_tab = self.visible_playlist_tab()
visible_tab_id = visible_tab.playlist_id
playlists = [p for p in Playlists.get_all(session)
if p.id != self.visible_playlist_tab().id]
if p.id != visible_tab_id]
dlg = SelectPlaylistDialog(self, playlists=playlists)
dlg.exec()
if not dlg.plid:
return
# TODO: just update dest playlist and call populate if
# visible
# Update database for both source and destination playlists
rows = visible_tab.get_selected_rows()
for row in rows:
PlaylistTracks.move_track(
session, visible_tab.playlist_id, row, dlg.plid
)
# If destination playlist is visible, we need to add the moved
# tracks to it. If not, they will be automatically loaded when
# the playlistis opened.
# Update destination playlist_tab if visible (if not visible, it
# will be re-populated when it is opened)
destination_visible_playlist_tab = None
for tab in range(self.tabPlaylist.count()):
# Non-playlist tabs won't have a 'playlist' attribute
if not hasattr(self.tabPlaylist.widget(tab), 'playlist'):
# Non-playlist tabs won't have a 'playlist_id' attribute
if not hasattr(self.tabPlaylist.widget(tab), 'playlist_id'):
continue
if self.tabPlaylist.widget(tab).id == dlg.plid:
if self.tabPlaylist.widget(tab).playlist_id == dlg.plid:
destination_visible_playlist_tab = (
self.tabPlaylist.widget(tab))
break
rows = []
for (row, track) in (
self.visible_playlist_tab().get_selected_rows_and_tracks(
session)
):
rows.append(row)
if destination_visible_playlist_tab:
# Insert with repaint=False to not update database
destination_visible_playlist_tab.insert_track(
session, track, repaint=False)
# Update database for both source and destination playlists
PlaylistTracks.move_rows(
session, self.visible_playlist_tab().id, rows, dlg.plid)
# Update destination playlist if visible
if destination_visible_playlist_tab:
destination_visible_playlist_tab.update_display()
if destination_visible_playlist_tab:
destination_visible_playlist_tab.populate(session, dlg.plid)
# Update source playlist
self.visible_playlist_tab().remove_rows(rows)
@ -577,8 +566,8 @@ class Window(QMainWindow, Ui_MainWindow):
dlg = SelectPlaylistDialog(self, playlists=playlists)
dlg.exec()
if dlg.plid:
playlist = Playlists.get_by_id(session, dlg.plid)
self.create_playlist_tab(session, playlist)
p = Playlists.get_by_id(session=session, playlist_id=dlg.plid)
self.create_playlist_tab(session, p)
def select_next_row(self) -> None:
"""Select next or first row in playlist"""

View File

@ -65,13 +65,12 @@ class PlaylistTab(QTableWidget):
ROW_METADATA = Qt.UserRole
CONTENT_OBJECT = Qt.UserRole + 1
def __init__(self, parent: QMainWindow, session: Session,
playlist: Playlists, *args, **kwargs):
def __init__(self, musicmuster: QMainWindow, session: Session,
playlist_id: int, *args, **kwargs):
super().__init__(*args, **kwargs)
self.parent: QMainWindow = parent # The MusicMuster process
self.playlist: Playlists = playlist
self.playlist.mark_open(session)
self.musicmuster: QMainWindow = musicmuster
self.playlist_id: int = playlist_id
self.menu: Optional[QMenu] = None
self.current_track_start_time: Optional[datetime] = None
@ -140,13 +139,10 @@ class PlaylistTab(QTableWidget):
self.cellEditingEnded.connect(self._cell_edit_ended)
# Now load our tracks and notes
self._populate(session, playlist)
self.populate(session, self.playlist_id)
def __repr__(self) -> str:
return (
f"<PlaylistTab(id={self.playlist.id}, "
f"name={self.playlist.name}>"
)
return (f"<PlaylistTab(id={self.playlist_id}")
# ########## Events ##########
@ -285,7 +281,7 @@ class PlaylistTab(QTableWidget):
if ok:
with Session() as session:
note: Notes = Notes(
session, self.playlist.id, row, dlg.textValue())
session, self.playlist_id, row, dlg.textValue())
self._insert_note(session, note, row, True) # checked
def get_selected_row(self) -> Optional[int]:
@ -296,19 +292,11 @@ class PlaylistTab(QTableWidget):
else:
return self.selectionModel().selectedRows()[0].row()
def get_selected_rows_and_tracks(self, session: Session) \
-> Optional[List[Tuple[int, Tracks]]]:
"""Return a list of selected (row-number, track) tuples"""
def get_selected_rows(self) -> List[int]:
"""Return a list of selected row numbers"""
if not self.selectionModel().hasSelection():
return None
result = []
for row in [r.row() for r in self.selectionModel().selectedRows()]:
result.append((row, self._get_row_object(row, session)))
return result
rows = self.selectionModel().selectedRows()
return [row.row() for row in rows]
def get_selected_title(self) -> Optional[str]:
"""Return title of selected row or None"""
@ -448,6 +436,49 @@ class PlaylistTab(QTableWidget):
self._clear_current_track_row()
self.current_track_start_time = None
def populate(self, session: Session, playlist_id: int) -> None:
"""
Populate from the associated playlist ID
We don't mandate that an item will be on its specified row, only
that it will be above larger-numbered row items, and below
lower-numbered ones.
"""
data: List[Union[Tuple[List[int], Tracks], Tuple[List[int], Notes]]] \
= []
item: Union[Notes, Tracks]
note: Notes
row: int
track: Tracks
playlist = Playlists.get_by_id(session, playlist_id)
for row, track in playlist.tracks.items():
data.append(([row], track))
for note in playlist.notes:
data.append(([note.row], note))
# Clear playlist
self.setRowCount(0)
# Now add data in row order
for i in sorted(data, key=lambda x: x[0]):
item = i[1]
if isinstance(item, Tracks):
self.insert_track(session, item, repaint=False)
elif isinstance(item, Notes):
self._insert_note(session, item, repaint=False)
# Scroll to top
scroll_to: QTableWidgetItem = self.item(0, self.COL_TITLE)
self.scrollToItem(scroll_to, QAbstractItemView.PositionAtTop)
# We possibly don't need to save the playlist here, but row
# numbers may have changed during population, and it's cheap to do
self.save_playlist(session)
self.update_display(session)
def save_playlist(self, session) -> None:
"""
Save playlist to database.
@ -461,8 +492,7 @@ class PlaylistTab(QTableWidget):
changes.
"""
# Ensure we have a valid database class
session.add(self.playlist)
playlist = Playlists.get_by_id(session, self.playlist_id)
# Notes first
# Create dictionaries indexed by note_id
@ -477,7 +507,7 @@ class PlaylistTab(QTableWidget):
playlist_notes[note.id] = note
# Database
for note in self.playlist.notes:
for note in playlist.notes:
database_notes[note.id] = note
# We don't need to check for notes to add to the database as
@ -505,14 +535,14 @@ class PlaylistTab(QTableWidget):
# Tracks
# Remove all tracks from this playlist
self.playlist.remove_all_tracks(session)
playlist.remove_all_tracks(session)
# Iterate on-screen playlist and add tracks back in
for row in range(self.rowCount()):
if row in notes_rows:
continue
track_id: int = self.item(
row, self.COL_USERDATA).data(self.CONTENT_OBJECT)
self.playlist.add_track(session, track_id, row)
playlist.add_track(session, track_id, row)
def select_next_row(self) -> None:
"""
@ -616,17 +646,13 @@ class PlaylistTab(QTableWidget):
- Show unplayed tracks in bold
"""
if self.playlist not in session:
session.add(self.playlist)
DEBUG(f"playlist.update_display [{self.playlist=}]")
# Clear selection if required
if clear_selection:
self.clearSelection()
current_row: Optional[int] = self._get_current_track_row()
next_row: Optional[int] = self._get_next_track_row()
notes: Optional[List[int]] = self._get_notes_rows()
notes: List[int] = self._get_notes_rows()
played: Optional[List[int]] = self._get_played_track_rows()
unreadable: Optional[List[int]] = self._get_unreadable_track_rows()
@ -871,7 +897,7 @@ class PlaylistTab(QTableWidget):
with Session() as session:
self.update_display(session)
self.parent.enable_play_next_controls()
self.musicmuster.enable_play_next_controls()
def _cell_edit_started(self, row: int, column: int) -> None:
"""
@ -883,7 +909,28 @@ class PlaylistTab(QTableWidget):
self.editing_cell = True
# Disable play controls so that keyboard input doesn't disturb playing
self.parent.disable_play_next_controls()
self.musicmuster.disable_play_next_controls()
def _clear_current_track_row(self) -> None:
"""
Clear current row if there is one.
"""
current_row: Optional[int] = self._get_current_track_row()
if current_row is not None:
self._meta_clear_attribute(current_row, RowMeta.CURRENT)
# Reset row colour
if current_row % 2:
self._set_row_colour(
current_row, QColor(Config.COLOUR_ODD_PLAYLIST))
else:
self._set_row_colour(
current_row, QColor(Config.COLOUR_EVEN_PLAYLIST))
def _clear_played_row_status(self, row: int) -> None:
"""Clear played status on row"""
self._meta_clear_attribute(row, RowMeta.PLAYED)
def _delete_rows(self) -> None:
"""Delete mutliple rows"""
@ -932,6 +979,67 @@ class PlaylistTab(QTableWidget):
return (index.row() + 1 if self._is_below(event.pos(), index)
else index.row())
def _edit_cell(self, mi): # review
"""Called when table is double-clicked"""
row = mi.row()
column = mi.column()
item = self.item(row, column)
if column in [self.COL_TITLE, self.COL_ARTIST]:
self.editItem(item)
@staticmethod
def _file_is_readable(path: str) -> bool:
"""
Returns True if track path is readable, else False
vlc cannot read files with a colon in the path
"""
if os.access(path, os.R_OK):
if ':' not in path:
return True
return False
def _find_next_track_row(self, starting_row: int = None) -> Optional[int]:
"""
Find next track to play. If a starting row is given, start there;
else if there's a track selected, start looking from next track;
otherwise, start from top. Skip rows already played.
If not found, return None.
If found, return row number.
"""
if starting_row is None:
current_row = self._get_current_track_row()
if current_row is not None:
starting_row = current_row + 1
else:
starting_row = 0
notes_rows = self._get_notes_rows()
played_rows = self._get_played_track_rows()
for row in range(starting_row, self.rowCount()):
if row in notes_rows or row in played_rows:
continue
else:
return row
return None
def _get_current_track_row(self) -> Optional[int]:
"""Return row marked as current, or None"""
return self._meta_search(RowMeta.CURRENT)
def _get_next_track_row(self) -> Optional[int]:
"""Return row marked as next, or None"""
return self._meta_search(RowMeta.NEXT)
@staticmethod
def _get_note_text_time(text: str) -> Optional[datetime]:
"""Return time specified at the end of text"""
@ -945,6 +1053,11 @@ class PlaylistTab(QTableWidget):
except ValueError:
return None
def _get_notes_rows(self) -> List[int]:
"""Return rows marked as notes, or None"""
return self._meta_search(RowMeta.NOTE, one=False)
def _get_row_end_time(self, row) -> Optional[datetime]:
"""
Return row end time as string
@ -961,14 +1074,6 @@ class PlaylistTab(QTableWidget):
except ValueError:
return None
def _get_row_track_object(self, row: int, session: Session) \
-> Optional[Tracks]:
"""Return track associated with this row"""
track_id = self.item(row, self.COL_USERDATA).data(self.CONTENT_OBJECT)
track = Tracks.get_by_id(session, track_id)
return track
def _get_row_notes_object(self, row: int, session: Session) \
-> Optional[Notes]:
"""Return note associated with this row"""
@ -977,6 +1082,11 @@ class PlaylistTab(QTableWidget):
note = Notes.get_by_id(session, note_id)
return note
def _get_played_track_rows(self) -> Optional[List[int]]:
"""Return rows marked as played, or None"""
return self._meta_search(RowMeta.PLAYED, one=False)
def _get_row_start_time(self, row: int) -> Optional[datetime]:
try:
if self.item(row, self.COL_START_TIME):
@ -989,6 +1099,24 @@ class PlaylistTab(QTableWidget):
except ValueError:
return None
def _get_row_track_object(self, row: int, session: Session) \
-> Optional[Tracks]:
"""Return track associated with this row"""
track_id = self.item(row, self.COL_USERDATA).data(self.CONTENT_OBJECT)
track = Tracks.get_by_id(session, track_id)
return track
def _get_track_rows(self) -> Optional[List[int]]:
"""Return rows marked as tracks, or None"""
return self._meta_notset(RowMeta.NOTE)
def _get_unreadable_track_rows(self) -> Optional[List[int]]:
"""Return rows marked as unreadable, or None"""
return self._meta_search(RowMeta.UNREADABLE, one=False)
def _info_row(self, row: int) -> None:
"""Display popup with info re row"""
@ -1071,43 +1199,6 @@ class PlaylistTab(QTableWidget):
and pos.y() >= rect.center().y() # noqa W503
)
def _edit_cell(self, mi): # review
"""Called when table is double-clicked"""
row = mi.row()
column = mi.column()
item = self.item(row, column)
if column in [self.COL_TITLE, self.COL_ARTIST]:
self.editItem(item)
def _find_next_track_row(self, starting_row: int = None) -> Optional[int]:
"""
Find next track to play. If a starting row is given, start there;
else if there's a track selected, start looking from next track;
otherwise, start from top. Skip rows already played.
If not found, return None.
If found, return row number.
"""
if starting_row is None:
current_row = self._get_current_track_row()
if current_row is not None:
starting_row = current_row + 1
else:
starting_row = 0
notes_rows = self._get_notes_rows()
played_rows = self._get_played_track_rows()
for row in range(starting_row, self.rowCount()):
if row in notes_rows or row in played_rows:
continue
else:
return row
return None
def _meta_clear_attribute(self, row: int, attribute: int) -> None:
"""Clear given metadata for row"""
@ -1118,22 +1209,6 @@ class PlaylistTab(QTableWidget):
self.item(row, self.COL_USERDATA).setData(
self.ROW_METADATA, new_metadata)
def _clear_current_track_row(self) -> None:
"""
Clear current row if there is one.
"""
current_row: Optional[int] = self._get_current_track_row()
if current_row is not None:
self._meta_clear_attribute(current_row, RowMeta.CURRENT)
# Reset row colour
if current_row % 2:
self._set_row_colour(
current_row, QColor(Config.COLOUR_ODD_PLAYLIST))
else:
self._set_row_colour(
current_row, QColor(Config.COLOUR_EVEN_PLAYLIST))
def _meta_clear_next(self) -> None:
"""
Clear next row if there is one.
@ -1143,46 +1218,11 @@ class PlaylistTab(QTableWidget):
if next_row is not None:
self._meta_clear_attribute(next_row, RowMeta.NEXT)
def _clear_played_row_status(self, row: int) -> None:
"""Clear played status on row"""
self._meta_clear_attribute(row, RowMeta.PLAYED)
def _meta_get(self, row: int) -> Optional[int]:
"""Return row metadata"""
return self.item(row, self.COL_USERDATA).data(self.ROW_METADATA)
def _get_current_track_row(self) -> Optional[int]:
"""Return row marked as current, or None"""
return self._meta_search(RowMeta.CURRENT)
def _get_next_track_row(self) -> Optional[int]:
"""Return row marked as next, or None"""
return self._meta_search(RowMeta.NEXT)
def _get_notes_rows(self) -> Optional[List[int]]:
"""Return rows marked as notes, or None"""
return self._meta_search(RowMeta.NOTE, one=False)
def _get_track_rows(self) -> Optional[List[int]]:
"""Return rows marked as tracks, or None"""
return self._meta_notset(RowMeta.NOTE)
def _get_played_track_rows(self) -> Optional[List[int]]:
"""Return rows marked as played, or None"""
return self._meta_search(RowMeta.PLAYED, one=False)
def _get_unreadable_track_rows(self) -> Optional[List[int]]:
"""Return rows marked as unreadable, or None"""
return self._meta_search(RowMeta.UNREADABLE, one=False)
def _meta_notset(self, metadata: int) -> Union[List[int]]:
"""
Search rows for metadata not set.
@ -1243,6 +1283,21 @@ class PlaylistTab(QTableWidget):
self.item(row, self.COL_USERDATA).setData(
self.ROW_METADATA, new_metadata)
def _rescan(self, row: int) -> None:
"""
If passed row is track row, rescan it.
Otherwise, return None.
"""
DEBUG(f"_rescan({row=})")
with Session() as session:
for row in self._get_track_rows():
track: Tracks = self._get_row_track_object(row, session)
if track:
track.rescan(session)
self._update_row(session, row, track)
def _set_current_track_row(self, row: int) -> None:
"""Mark this row as current track"""
@ -1270,65 +1325,6 @@ class PlaylistTab(QTableWidget):
self._meta_set_attribute(row, RowMeta.UNREADABLE)
def _populate(self, session: Session, playlist: Playlists) -> None:
"""
Populate from the associated playlist object
We don't mandate that an item will be on its specified row, only
that it will be above larger-numbered row items, and below
lower-numbered ones.
"""
data: List[Union[Tuple[List[int], Tracks], Tuple[List[int], Notes]]] \
= []
item: Union[Notes, Tracks]
note: Notes
row: int
track: Tracks
if playlist not in session:
session.add(playlist)
for row, track in self.playlist.tracks.items():
data.append(([row], track))
for note in self.playlist.notes:
data.append(([note.row], note))
# Clear playlist
self.setRowCount(0)
# Now add data in row order
for i in sorted(data, key=lambda x: x[0]):
item = i[1]
if isinstance(item, Tracks):
self.insert_track(session, item, repaint=False)
elif isinstance(item, Notes):
self._insert_note(session, item, repaint=False)
# Scroll to top
scroll_to: QTableWidgetItem = self.item(0, self.COL_TITLE)
self.scrollToItem(scroll_to, QAbstractItemView.PositionAtTop)
# We possibly don't need to save the playlist here, but row
# numbers may have changed during population, and it's cheap to do
self.save_playlist(session)
self.update_display(session)
def _rescan(self, row: int) -> None:
"""
If passed row is track row, rescan it.
Otherwise, return None.
"""
DEBUG(f"_rescan({row=})")
with Session() as session:
for row in self._get_track_rows():
track: Tracks = self._get_row_track_object(row, session)
if track:
track.rescan(session)
self._update_row(session, row, track)
def _select_event(self) -> None:
"""
Called when item selection changes.
@ -1346,10 +1342,31 @@ class PlaylistTab(QTableWidget):
# Only paint message if there are selected track rows
if ms > 0:
self.parent.lblSumPlaytime.setText(
self.musicmuster.lblSumPlaytime.setText(
f"Selected duration: {helpers.ms_to_mmss(ms)}")
else:
self.parent.lblSumPlaytime.setText("")
self.musicmuster.lblSumPlaytime.setText("")
def _select_tracks(self, played: bool) -> None:
"""
Select all played (played=True) or unplayed (played=False)
tracks in playlist
"""
# Need to allow multiple rows to be selected
self.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection)
notes_rows: List[int] = self._get_notes_rows()
self.clearSelection()
played_rows: List[int] = self._get_played_track_rows()
for row in range(self.rowCount()):
if row in notes_rows:
continue
if row in played_rows == played:
self.selectRow(row)
# Reset extended selection
self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
def _set_column_widths(self) -> None:
"""Column widths from settings"""
@ -1394,7 +1411,7 @@ class PlaylistTab(QTableWidget):
self._set_next_track_row(row)
# Notify musicmuster
self.parent.this_is_the_next_track(self, track)
self.musicmuster.this_is_the_next_track(self, track)
# Update display
self.update_display(session)
@ -1453,41 +1470,6 @@ class PlaylistTab(QTableWidget):
item: QTableWidgetItem = QTableWidgetItem(time_str)
self.setItem(row, self.COL_START_TIME, item)
def _select_tracks(self, played: bool) -> None:
"""
Select all played (played=True) or unplayed (played=False)
tracks in playlist
"""
# Need to allow multiple rows to be selected
self.setSelectionMode(QtWidgets.QAbstractItemView.MultiSelection)
notes_rows: List[int] = self._get_notes_rows()
self.clearSelection()
played_rows: List[int] = self._get_played_track_rows()
for row in range(self.rowCount()):
if row in notes_rows:
continue
if row in played_rows == played:
self.selectRow(row)
# Reset extended selection
self.setSelectionMode(QtWidgets.QAbstractItemView.ExtendedSelection)
@staticmethod
def _file_is_readable(path: str) -> bool:
"""
Returns True if track path is readable, else False
vlc cannot read files with a colon in the path
"""
if os.access(path, os.R_OK):
if ':' not in path:
return True
return False
def _update_row(self, session, row: int, track: Tracks) -> None:
"""
Update the passed row with info from the passed track.

27
poetry.lock generated
View File

@ -217,7 +217,7 @@ python-versions = ">=3.5, <4"
name = "mypy"
version = "0.931"
description = "Optional static typing for Python"
category = "main"
category = "dev"
optional = false
python-versions = ">=3.6"
@ -234,7 +234,7 @@ python2 = ["typed-ast (>=1.4.0,<2)"]
name = "mypy-extensions"
version = "0.4.3"
description = "Experimental type system extensions for programs checked with the mypy typechecker."
category = "main"
category = "dev"
optional = false
python-versions = "*"
@ -404,6 +404,17 @@ category = "main"
optional = false
python-versions = ">=3.5"
[[package]]
name = "pyqt5-stubs"
version = "5.15.2.0"
description = "PEP561 stub files for the PyQt5 framework"
category = "dev"
optional = false
python-versions = ">= 3.5"
[package.extras]
build = ["docker (==4.2.0)"]
[[package]]
name = "pyqtwebengine"
version = "5.15.5"
@ -513,7 +524,7 @@ sqlcipher = ["sqlcipher3-binary"]
name = "sqlalchemy-stubs"
version = "0.4"
description = "SQLAlchemy stubs and mypy plugin"
category = "main"
category = "dev"
optional = false
python-versions = "*"
@ -560,7 +571,7 @@ python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
name = "tomli"
version = "2.0.1"
description = "A lil' TOML parser"
category = "main"
category = "dev"
optional = false
python-versions = ">=3.7"
@ -579,7 +590,7 @@ test = ["pytest"]
name = "typing-extensions"
version = "4.1.1"
description = "Backported and Experimental Type Hints for Python 3.6+"
category = "main"
category = "dev"
optional = false
python-versions = ">=3.6"
@ -594,7 +605,7 @@ python-versions = "*"
[metadata]
lock-version = "1.1"
python-versions = "^3.9"
content-hash = "6ea248abf2050b4d2fb08baaf78fee1b358e43109bb1bbf202a4d3e78a35ec53"
content-hash = "c9f7a8c9a15812c1f3affeaa301fe6e6310388cca41a229bdb97fdd58c765e6f"
[metadata.files]
alembic = [
@ -910,6 +921,10 @@ pyqt5-sip = [
{file = "PyQt5_sip-12.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:989d51c41456cc496cb96f0b341464932b957040d26561f0bb4cf5a0914d6b36"},
{file = "PyQt5_sip-12.9.1.tar.gz", hash = "sha256:2f24f299b44c511c23796aafbbb581bfdebf78d0905657b7cee2141b4982030e"},
]
pyqt5-stubs = [
{file = "PyQt5-stubs-5.15.2.0.tar.gz", hash = "sha256:dc0dea66f02fe297fb0cddd5767fbf58275b54ecb67f69ebeb994e1553bfb9b2"},
{file = "PyQt5_stubs-5.15.2.0-py3-none-any.whl", hash = "sha256:4b750d04ffca1bb188615d1a4e7d655a1d81d30df6ee3488f0adfe09b78e3d36"},
]
pyqtwebengine = [
{file = "PyQtWebEngine-5.15.5-cp36-abi3-macosx_10_13_x86_64.whl", hash = "sha256:5c77f71d88d871bc7400c68ef6433fadc5bd57b86d1a9c4d8094cea42f3607f1"},
{file = "PyQtWebEngine-5.15.5-cp36-abi3-manylinux1_x86_64.whl", hash = "sha256:782aeee6bc8699bc029fe5c169a045c2bc9533d781cf3f5e9fb424b85a204e68"},

View File

@ -17,15 +17,14 @@ psutil = "^5.9.0"
PyQtWebEngine = "^5.15.5"
pydub = "^0.25.1"
PyQt5-sip = "^12.9.1"
mypy = "^0.931"
sqlalchemy-stubs = "^0.4"
[tool.poetry.dev-dependencies]
mypy = "^0.931"
pytest = "^7.0.0"
ipdb = "^0.13.9"
sqlalchemy-stubs = "^0.4"
pytest-qt = "^4.0.2"
sqlalchemy-stubs = "^0.4"
PyQt5-stubs = "^5.15.2"
mypy = "^0.931"
[build-system]
requires = ["poetry-core>=1.0.0"]