Compare commits
7 Commits
262ab202fc
...
e3d20c9bdc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e3d20c9bdc | ||
|
|
5add1f01c6 | ||
|
|
88e638a56e | ||
|
|
4ca5eb24c3 | ||
|
|
05ef2d766c | ||
|
|
db547cbdb7 | ||
|
|
005d17ee0a |
@ -83,7 +83,11 @@ class MusicMusterSignals(QObject):
|
|||||||
add_track_to_playlist_signal = pyqtSignal(int, int, int, str)
|
add_track_to_playlist_signal = pyqtSignal(int, int, int, str)
|
||||||
enable_escape_signal = pyqtSignal(bool)
|
enable_escape_signal = pyqtSignal(bool)
|
||||||
next_track_changed_signal = pyqtSignal()
|
next_track_changed_signal = pyqtSignal()
|
||||||
|
search_songfacts_signal = pyqtSignal(str)
|
||||||
|
search_wikipedia_signal = pyqtSignal(str)
|
||||||
|
show_warning_signal = pyqtSignal(str, str)
|
||||||
span_cells_signal = pyqtSignal(int, int, int, int)
|
span_cells_signal = pyqtSignal(int, int, int, int)
|
||||||
|
status_message_signal = pyqtSignal(str, int)
|
||||||
|
|
||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|||||||
@ -1 +0,0 @@
|
|||||||
ui/icons_rc.py
|
|
||||||
@ -8,6 +8,8 @@ from PyQt6.QtWebEngineWidgets import QWebEngineView
|
|||||||
from PyQt6.QtWidgets import QTabWidget
|
from PyQt6.QtWidgets import QTabWidget
|
||||||
from config import Config
|
from config import Config
|
||||||
|
|
||||||
|
from classes import MusicMusterSignals
|
||||||
|
|
||||||
|
|
||||||
class InfoTabs(QTabWidget):
|
class InfoTabs(QTabWidget):
|
||||||
"""
|
"""
|
||||||
@ -17,7 +19,9 @@ class InfoTabs(QTabWidget):
|
|||||||
def __init__(self, parent=None) -> None:
|
def __init__(self, parent=None) -> None:
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
|
|
||||||
# Dictionary to record when tabs were last updated (so we can
|
self.signals = MusicMusterSignals()
|
||||||
|
self.signals.search_songfacts_signal.connect(self.open_in_songfacts)
|
||||||
|
self.signals.search_wikipedia_signal.connect(self.open_in_wikipedia)
|
||||||
# re-use the oldest one later)
|
# re-use the oldest one later)
|
||||||
self.last_update: Dict[QWebEngineView, datetime] = {}
|
self.last_update: Dict[QWebEngineView, datetime] = {}
|
||||||
self.tabtitles: Dict[int, str] = {}
|
self.tabtitles: Dict[int, str] = {}
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from os.path import basename
|
|
||||||
from time import sleep
|
from time import sleep
|
||||||
from typing import (
|
from typing import (
|
||||||
cast,
|
cast,
|
||||||
@ -74,7 +73,7 @@ from ui.downloadcsv_ui import Ui_DateSelect # type: ignore
|
|||||||
from ui.main_window_ui import Ui_MainWindow # type: ignore
|
from ui.main_window_ui import Ui_MainWindow # type: ignore
|
||||||
from utilities import check_db, update_bitrates
|
from utilities import check_db, update_bitrates
|
||||||
import helpers
|
import helpers
|
||||||
import icons_rc # noqa F401
|
from ui import icons_rc # noqa F401
|
||||||
import music
|
import music
|
||||||
|
|
||||||
|
|
||||||
@ -145,46 +144,6 @@ class CartButton(QPushButton):
|
|||||||
self.pgb.setGeometry(0, 0, self.width(), 10)
|
self.pgb.setGeometry(0, 0, self.width(), 10)
|
||||||
|
|
||||||
|
|
||||||
class ImportTrack(QObject):
|
|
||||||
import_error = pyqtSignal(str)
|
|
||||||
importing = pyqtSignal(str)
|
|
||||||
finished = pyqtSignal(PlaylistTab)
|
|
||||||
|
|
||||||
def __init__(self, playlist: PlaylistTab, filenames: list, row: int) -> None:
|
|
||||||
super().__init__()
|
|
||||||
self.filenames = filenames
|
|
||||||
self.playlist = playlist
|
|
||||||
self.row = row
|
|
||||||
|
|
||||||
def run(self):
|
|
||||||
"""
|
|
||||||
Create track objects from passed files and add to visible playlist
|
|
||||||
"""
|
|
||||||
|
|
||||||
target_row = self.row
|
|
||||||
with Session() as session:
|
|
||||||
for fname in self.filenames:
|
|
||||||
self.importing.emit(f"Importing {basename(fname)}")
|
|
||||||
metadata = helpers.get_file_metadata(fname)
|
|
||||||
try:
|
|
||||||
track = Tracks(session, **metadata)
|
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
return
|
|
||||||
helpers.normalise_track(track.path)
|
|
||||||
self.playlist.insert_track(session, track, target_row)
|
|
||||||
# Insert next row under this one
|
|
||||||
target_row += 1
|
|
||||||
# We're importing potentially multiple tracks in a loop.
|
|
||||||
# If there's an error adding the track to the Tracks
|
|
||||||
# table, the session will rollback, thus losing any
|
|
||||||
# previous additions in this loop. So, commit now to
|
|
||||||
# lock in what we've just done.
|
|
||||||
session.commit()
|
|
||||||
self.playlist.save_playlist(session)
|
|
||||||
self.finished.emit(self.playlist)
|
|
||||||
|
|
||||||
|
|
||||||
class Window(QMainWindow, Ui_MainWindow):
|
class Window(QMainWindow, Ui_MainWindow):
|
||||||
def __init__(self, parent=None, *args, **kwargs) -> None:
|
def __init__(self, parent=None, *args, **kwargs) -> None:
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
@ -476,7 +435,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
closing_tab_playlist_id = self.tabPlaylist.widget(tab_index).playlist_id
|
closing_tab_playlist_id = self.tabPlaylist.widget(tab_index).playlist_id
|
||||||
if current_track_playlist_id:
|
if current_track_playlist_id:
|
||||||
if closing_tab_playlist_id == current_track_playlist_id:
|
if closing_tab_playlist_id == current_track_playlist_id:
|
||||||
self.statusbar.showMessage("Can't close current track playlist", 5000)
|
self.show_status_message("Can't close current track playlist", 5000)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Record playlist as closed and update remaining playlist tabs
|
# Record playlist as closed and update remaining playlist tabs
|
||||||
@ -523,10 +482,10 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
self.actionResume.triggered.connect(self.resume)
|
self.actionResume.triggered.connect(self.resume)
|
||||||
self.actionSave_as_template.triggered.connect(self.save_as_template)
|
self.actionSave_as_template.triggered.connect(self.save_as_template)
|
||||||
self.actionSearch_title_in_Songfacts.triggered.connect(
|
self.actionSearch_title_in_Songfacts.triggered.connect(
|
||||||
lambda: self.tabPlaylist.currentWidget().lookup_row_in_songfacts()
|
self.lookup_row_in_songfacts
|
||||||
)
|
)
|
||||||
self.actionSearch_title_in_Wikipedia.triggered.connect(
|
self.actionSearch_title_in_Wikipedia.triggered.connect(
|
||||||
lambda: self.tabPlaylist.currentWidget().lookup_row_in_wikipedia()
|
self.lookup_row_in_wikipedia
|
||||||
)
|
)
|
||||||
self.actionSearch.triggered.connect(self.search_playlist)
|
self.actionSearch.triggered.connect(self.search_playlist)
|
||||||
self.actionSelect_duplicate_rows.triggered.connect(
|
self.actionSelect_duplicate_rows.triggered.connect(
|
||||||
@ -551,6 +510,8 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
|
|
||||||
self.signals.enable_escape_signal.connect(self.enable_escape)
|
self.signals.enable_escape_signal.connect(self.enable_escape)
|
||||||
self.signals.next_track_changed_signal.connect(self.update_headers)
|
self.signals.next_track_changed_signal.connect(self.update_headers)
|
||||||
|
self.signals.status_message_signal.connect(self.show_status_message)
|
||||||
|
self.signals.show_warning_signal.connect(self.show_warning)
|
||||||
|
|
||||||
self.timer10.timeout.connect(self.tick_10ms)
|
self.timer10.timeout.connect(self.tick_10ms)
|
||||||
self.timer500.timeout.connect(self.tick_500ms)
|
self.timer500.timeout.connect(self.tick_500ms)
|
||||||
@ -638,7 +599,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
self.actionPlay_next.setEnabled(False)
|
self.actionPlay_next.setEnabled(False)
|
||||||
self.statusbar.showMessage("Play controls: Disabled", 0)
|
self.show_status_message("Play controls: Disabled", 0)
|
||||||
|
|
||||||
def download_played_tracks(self) -> None:
|
def download_played_tracks(self) -> None:
|
||||||
"""Download a CSV of played tracks"""
|
"""Download a CSV of played tracks"""
|
||||||
@ -689,7 +650,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
self.actionPlay_next.setEnabled(True)
|
self.actionPlay_next.setEnabled(True)
|
||||||
self.statusbar.showMessage("Play controls: Enabled", 0)
|
self.show_status_message("Play controls: Enabled", 0)
|
||||||
|
|
||||||
def export_playlist_tab(self) -> None:
|
def export_playlist_tab(self) -> None:
|
||||||
"""Export the current playlist to an m3u file"""
|
"""Export the current playlist to an m3u file"""
|
||||||
@ -786,7 +747,21 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
txt = ""
|
txt = ""
|
||||||
tags = helpers.get_tags(fname)
|
tags = helpers.get_tags(fname)
|
||||||
title = tags["title"]
|
title = tags["title"]
|
||||||
|
if not title:
|
||||||
|
helpers.show_warning(
|
||||||
|
self,
|
||||||
|
"Problem with track file",
|
||||||
|
f"{fname} does not have a title tag",
|
||||||
|
)
|
||||||
|
continue
|
||||||
artist = tags["artist"]
|
artist = tags["artist"]
|
||||||
|
if not artist:
|
||||||
|
helpers.show_warning(
|
||||||
|
self,
|
||||||
|
"Problem with track file",
|
||||||
|
f"{fname} does not have an artist tag",
|
||||||
|
)
|
||||||
|
continue
|
||||||
count = 0
|
count = 0
|
||||||
possible_matches = Tracks.search_titles(session, title)
|
possible_matches = Tracks.search_titles(session, title)
|
||||||
if possible_matches:
|
if possible_matches:
|
||||||
@ -813,33 +788,10 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
continue
|
continue
|
||||||
new_tracks.append(fname)
|
new_tracks.append(fname)
|
||||||
|
|
||||||
# Import in separate thread
|
# Pass to model to manage the import
|
||||||
self.import_thread = QThread()
|
self.active_model().import_tracks(
|
||||||
self.worker = ImportTrack(
|
new_tracks, self.active_tab().get_selected_row_number()
|
||||||
self.active_tab(),
|
|
||||||
new_tracks,
|
|
||||||
self.active_tab().get_new_row_number(),
|
|
||||||
)
|
)
|
||||||
self.worker.moveToThread(self.import_thread)
|
|
||||||
self.import_thread.started.connect(self.worker.run)
|
|
||||||
self.worker.finished.connect(self.import_thread.quit)
|
|
||||||
self.worker.finished.connect(self.worker.deleteLater)
|
|
||||||
self.import_thread.finished.connect(self.import_thread.deleteLater)
|
|
||||||
self.worker.import_error.connect(
|
|
||||||
lambda msg: helpers.show_warning(
|
|
||||||
self, "Import error", "Error importing " + msg
|
|
||||||
)
|
|
||||||
)
|
|
||||||
self.worker.importing.connect(lambda msg: self.statusbar.showMessage(msg, 5000))
|
|
||||||
self.worker.finished.connect(self.import_complete)
|
|
||||||
self.import_thread.start()
|
|
||||||
|
|
||||||
def import_complete(self):
|
|
||||||
"""
|
|
||||||
Called by thread when track import complete
|
|
||||||
"""
|
|
||||||
|
|
||||||
self.statusbar.showMessage("Imports complete")
|
|
||||||
|
|
||||||
def insert_header(self) -> None:
|
def insert_header(self) -> None:
|
||||||
"""Show dialog box to enter header text and add to playlist"""
|
"""Show dialog box to enter header text and add to playlist"""
|
||||||
@ -887,6 +839,36 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
if record.f_int and record.f_int >= 0:
|
if record.f_int and record.f_int >= 0:
|
||||||
self.tabPlaylist.setCurrentIndex(record.f_int)
|
self.tabPlaylist.setCurrentIndex(record.f_int)
|
||||||
|
|
||||||
|
def lookup_row_in_songfacts(self) -> None:
|
||||||
|
"""
|
||||||
|
Display songfacts page for title in highlighted row
|
||||||
|
"""
|
||||||
|
|
||||||
|
row_number = self.active_tab().get_selected_row_number()
|
||||||
|
if row_number is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
track_info = self.active_model().get_row_info(row_number)
|
||||||
|
if track_info is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.signals.search_songfacts_signal.emit(track_info.title)
|
||||||
|
|
||||||
|
def lookup_row_in_wikipedia(self) -> None:
|
||||||
|
"""
|
||||||
|
Display Wikipedia page for title in highlighted row
|
||||||
|
"""
|
||||||
|
|
||||||
|
row_number = self.active_tab().get_selected_row_number()
|
||||||
|
if row_number is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
track_info = self.active_model().get_row_info(row_number)
|
||||||
|
if track_info is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.signals.search_wikipedia_signal.emit(track_info.title)
|
||||||
|
|
||||||
def move_playlist_rows(
|
def move_playlist_rows(
|
||||||
self, session: scoped_session, playlistrows: Sequence[PlaylistRows]
|
self, session: scoped_session, playlistrows: Sequence[PlaylistRows]
|
||||||
) -> None:
|
) -> None:
|
||||||
@ -1206,7 +1188,7 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
playlist_id = self.active_tab().playlist_id
|
playlist_id = self.active_tab().playlist_id
|
||||||
playlist = session.get(Playlists, playlist_id)
|
playlist = session.get(Playlists, playlist_id)
|
||||||
if playlist:
|
if playlist:
|
||||||
new_name = self.solicit_playlist_name(playlist.name)
|
new_name = self.solicit_playlist_name(session, playlist.name)
|
||||||
if new_name:
|
if new_name:
|
||||||
playlist.rename(session, new_name)
|
playlist.rename(session, new_name)
|
||||||
idx = self.tabBar.currentIndex()
|
idx = self.tabBar.currentIndex()
|
||||||
@ -1355,6 +1337,14 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
# self.tabPlaylist.setCurrentWidget(self.current_track.playlist_tab)
|
# self.tabPlaylist.setCurrentWidget(self.current_track.playlist_tab)
|
||||||
# self.tabPlaylist.currentWidget().scroll_current_to_top()
|
# self.tabPlaylist.currentWidget().scroll_current_to_top()
|
||||||
|
|
||||||
|
def show_warning(self, title: str, body: str) -> None:
|
||||||
|
"""
|
||||||
|
Display a warning dialog
|
||||||
|
"""
|
||||||
|
|
||||||
|
print(f"show_warning({title=}, {body=})")
|
||||||
|
QMessageBox.warning(self, title, body)
|
||||||
|
|
||||||
def show_next(self) -> None:
|
def show_next(self) -> None:
|
||||||
"""Scroll to show next track"""
|
"""Scroll to show next track"""
|
||||||
|
|
||||||
@ -1364,6 +1354,13 @@ class Window(QMainWindow, Ui_MainWindow):
|
|||||||
# self.tabPlaylist.setCurrentWidget(self.next_track.playlist_tab)
|
# self.tabPlaylist.setCurrentWidget(self.next_track.playlist_tab)
|
||||||
# self.tabPlaylist.currentWidget().scroll_next_to_top()
|
# self.tabPlaylist.currentWidget().scroll_next_to_top()
|
||||||
|
|
||||||
|
def show_status_message(self, message: str, timing: int) -> None:
|
||||||
|
"""
|
||||||
|
Show status message in status bar for timing milliseconds
|
||||||
|
"""
|
||||||
|
|
||||||
|
self.statusbar.showMessage(message, timing)
|
||||||
|
|
||||||
def solicit_playlist_name(
|
def solicit_playlist_name(
|
||||||
self, session: scoped_session, default: str = ""
|
self, session: scoped_session, default: str = ""
|
||||||
) -> Optional[str]:
|
) -> Optional[str]:
|
||||||
|
|||||||
@ -2,12 +2,16 @@ from dataclasses import dataclass
|
|||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from enum import auto, Enum
|
from enum import auto, Enum
|
||||||
from operator import attrgetter
|
from operator import attrgetter
|
||||||
|
from os.path import basename
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
from PyQt6.QtCore import (
|
from PyQt6.QtCore import (
|
||||||
|
pyqtSignal,
|
||||||
QAbstractTableModel,
|
QAbstractTableModel,
|
||||||
QModelIndex,
|
QModelIndex,
|
||||||
|
QObject,
|
||||||
Qt,
|
Qt,
|
||||||
|
QThread,
|
||||||
QVariant,
|
QVariant,
|
||||||
)
|
)
|
||||||
from PyQt6.QtGui import (
|
from PyQt6.QtGui import (
|
||||||
@ -22,8 +26,10 @@ from dbconfig import scoped_session, Session
|
|||||||
from helpers import (
|
from helpers import (
|
||||||
file_is_unreadable,
|
file_is_unreadable,
|
||||||
get_embedded_time,
|
get_embedded_time,
|
||||||
|
get_file_metadata,
|
||||||
get_relative_date,
|
get_relative_date,
|
||||||
open_in_audacity,
|
open_in_audacity,
|
||||||
|
normalise_track,
|
||||||
ms_to_mmss,
|
ms_to_mmss,
|
||||||
set_track_metadata,
|
set_track_metadata,
|
||||||
)
|
)
|
||||||
@ -150,7 +156,9 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
if playlist_id != self.playlist_id:
|
if playlist_id != self.playlist_id:
|
||||||
return
|
return
|
||||||
|
|
||||||
self.insert_row(proposed_row_number=new_row_number, track_id=track_id, note=note)
|
self.insert_row(
|
||||||
|
proposed_row_number=new_row_number, track_id=track_id, note=note
|
||||||
|
)
|
||||||
|
|
||||||
def add_track_to_header(
|
def add_track_to_header(
|
||||||
self,
|
self,
|
||||||
@ -634,6 +642,28 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
|
|
||||||
return prd.note
|
return prd.note
|
||||||
|
|
||||||
|
def import_tracks(
|
||||||
|
self, new_tracks: List[str], proposed_row_number: Optional[int]
|
||||||
|
) -> None:
|
||||||
|
"""
|
||||||
|
Import the file paths listed in new_tracks. The files have already been sanitised
|
||||||
|
so no further checks needed. Import in a separate thread as this is slow.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Import in separate thread
|
||||||
|
self.import_thread = QThread()
|
||||||
|
self.worker = ImportTrack(
|
||||||
|
self,
|
||||||
|
new_tracks,
|
||||||
|
proposed_row_number,
|
||||||
|
)
|
||||||
|
self.worker.moveToThread(self.import_thread)
|
||||||
|
self.import_thread.started.connect(self.worker.run)
|
||||||
|
self.worker.import_finished.connect(self.import_thread.quit)
|
||||||
|
self.worker.import_finished.connect(self.worker.deleteLater)
|
||||||
|
self.import_thread.finished.connect(self.import_thread.deleteLater)
|
||||||
|
self.import_thread.start()
|
||||||
|
|
||||||
def is_header_row(self, row_number: int) -> bool:
|
def is_header_row(self, row_number: int) -> bool:
|
||||||
"""
|
"""
|
||||||
Return True if row is a header row, else False
|
Return True if row is a header row, else False
|
||||||
@ -824,11 +854,16 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
|
|
||||||
def remove_track(self, row_number: int) -> None:
|
def remove_track(self, row_number: int) -> None:
|
||||||
"""
|
"""
|
||||||
Remove track from row
|
Remove track from row, retaining row as a header row
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# TODO
|
with Session() as session:
|
||||||
print(f"remove_track({row_number=})")
|
plr = session.get(PlaylistRows, self.playlist_rows[row_number].plrid)
|
||||||
|
if plr:
|
||||||
|
plr.track_id = None
|
||||||
|
self.refresh_row(session, row_number)
|
||||||
|
self.invalidate_row(row_number)
|
||||||
|
|
||||||
|
|
||||||
def rescan_track(self, row_number: int) -> None:
|
def rescan_track(self, row_number: int) -> None:
|
||||||
"""
|
"""
|
||||||
@ -882,6 +917,7 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
return
|
return
|
||||||
track_sequence.next = PlaylistTrack()
|
track_sequence.next = PlaylistTrack()
|
||||||
self.invalidate_row(next_row_was)
|
self.invalidate_row(next_row_was)
|
||||||
|
self.signals.next_track_changed_signal.emit()
|
||||||
return
|
return
|
||||||
|
|
||||||
# Update playing_trtack
|
# Update playing_trtack
|
||||||
@ -905,6 +941,7 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
return
|
return
|
||||||
track_sequence.next.set_plr(session, plr)
|
track_sequence.next.set_plr(session, plr)
|
||||||
self.signals.next_track_changed_signal.emit()
|
self.signals.next_track_changed_signal.emit()
|
||||||
|
self.signals.search_wikipedia_signal.emit(self.playlist_rows[row_number].title)
|
||||||
self.invalidate_row(row_number)
|
self.invalidate_row(row_number)
|
||||||
self.update_track_times()
|
self.update_track_times()
|
||||||
|
|
||||||
@ -1079,3 +1116,46 @@ class PlaylistModel(QAbstractTableModel):
|
|||||||
self.index(updated_row, Col.START_TIME.value),
|
self.index(updated_row, Col.START_TIME.value),
|
||||||
self.index(updated_row, Col.END_TIME.value),
|
self.index(updated_row, Col.END_TIME.value),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ImportTrack(QObject):
|
||||||
|
import_finished = pyqtSignal()
|
||||||
|
|
||||||
|
def __init__(self, model: PlaylistModel, filenames: List[str], row_number) -> None:
|
||||||
|
super().__init__()
|
||||||
|
self.model = model
|
||||||
|
self.filenames = filenames
|
||||||
|
self.row_number = row_number
|
||||||
|
self.signals = MusicMusterSignals()
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
"""
|
||||||
|
Create track objects from passed files and add to visible playlist
|
||||||
|
"""
|
||||||
|
|
||||||
|
target_row = self.row_number
|
||||||
|
with Session() as session:
|
||||||
|
for fname in self.filenames:
|
||||||
|
self.signals.status_message_signal.emit(
|
||||||
|
f"Importing {basename(fname)}", 5000
|
||||||
|
)
|
||||||
|
metadata = get_file_metadata(fname)
|
||||||
|
try:
|
||||||
|
track = Tracks(session, **metadata)
|
||||||
|
except Exception as e:
|
||||||
|
self.signals.show_warning_signal.emit("Error importing track", e)
|
||||||
|
return
|
||||||
|
normalise_track(track.path)
|
||||||
|
self.model.insert_row(self.row_number, track.id)
|
||||||
|
# Insert next row under this one
|
||||||
|
target_row += 1
|
||||||
|
# We're importing potentially multiple tracks in a loop.
|
||||||
|
# If there's an error adding the track to the Tracks
|
||||||
|
# table, the session will rollback, thus losing any
|
||||||
|
# previous additions in this loop. So, commit now to
|
||||||
|
# lock in what we've just done.
|
||||||
|
session.commit()
|
||||||
|
self.signals.status_message_signal.emit(
|
||||||
|
f"{len(self.filenames)} tracks imported", 10000
|
||||||
|
)
|
||||||
|
self.import_finished.emit()
|
||||||
|
|||||||
@ -472,7 +472,7 @@ class PlaylistTab(QTableView):
|
|||||||
Set selected row as next track
|
Set selected row as next track
|
||||||
"""
|
"""
|
||||||
|
|
||||||
selected_row = self._get_selected_row()
|
selected_row = self.get_selected_row_number()
|
||||||
if selected_row is None:
|
if selected_row is None:
|
||||||
return
|
return
|
||||||
model = cast(PlaylistModel, self.model())
|
model = cast(PlaylistModel, self.model())
|
||||||
@ -541,13 +541,13 @@ class PlaylistTab(QTableView):
|
|||||||
# Mark unplayed
|
# Mark unplayed
|
||||||
if track_row and model.is_unplayed_row(row_number):
|
if track_row and model.is_unplayed_row(row_number):
|
||||||
self._add_context_menu(
|
self._add_context_menu(
|
||||||
"Mark unplayed", lambda: model.mark_unplayed(self._get_selected_rows())
|
"Mark unplayed", lambda: self._mark_as_unplayed(self._get_selected_rows())
|
||||||
)
|
)
|
||||||
|
|
||||||
# Unmark as next
|
# Unmark as next
|
||||||
if next_row:
|
if next_row:
|
||||||
self._add_context_menu(
|
self._add_context_menu(
|
||||||
"Unmark as next track", lambda: model.set_next_row(None)
|
"Unmark as next track", lambda: self._unmark_as_next()
|
||||||
)
|
)
|
||||||
|
|
||||||
# ----------------------
|
# ----------------------
|
||||||
@ -582,7 +582,7 @@ class PlaylistTab(QTableView):
|
|||||||
|
|
||||||
# Track path TODO
|
# Track path TODO
|
||||||
if track_row:
|
if track_row:
|
||||||
self._add_context_menu("Copy track path", lambda: print("Track path"))
|
self._add_context_menu("Copy track path", lambda: self._copy_path(row_number))
|
||||||
|
|
||||||
def _calculate_end_time(
|
def _calculate_end_time(
|
||||||
self, start: Optional[datetime], duration: int
|
self, start: Optional[datetime], duration: int
|
||||||
@ -624,7 +624,8 @@ class PlaylistTab(QTableView):
|
|||||||
to the clipboard. Otherwise, return None.
|
to the clipboard. Otherwise, return None.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
track_path = self._get_row_track_path(row_number)
|
model = cast(PlaylistModel, self.model())
|
||||||
|
track_path = model.get_row_info(row_number).path
|
||||||
if not track_path:
|
if not track_path:
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -638,8 +639,9 @@ class PlaylistTab(QTableView):
|
|||||||
track_path = track_path.replace(old, new)
|
track_path = track_path.replace(old, new)
|
||||||
|
|
||||||
cb = QApplication.clipboard()
|
cb = QApplication.clipboard()
|
||||||
cb.clear(mode=cb.Mode.Clipboard)
|
if cb:
|
||||||
cb.setText(track_path, mode=cb.Mode.Clipboard)
|
cb.clear(mode=cb.Mode.Clipboard)
|
||||||
|
cb.setText(track_path, mode=cb.Mode.Clipboard)
|
||||||
|
|
||||||
def _delete_rows(self) -> None:
|
def _delete_rows(self) -> None:
|
||||||
"""
|
"""
|
||||||
@ -663,18 +665,6 @@ class PlaylistTab(QTableView):
|
|||||||
model = cast(PlaylistModel, self.model())
|
model = cast(PlaylistModel, self.model())
|
||||||
model.delete_rows(self._get_selected_rows())
|
model.delete_rows(self._get_selected_rows())
|
||||||
|
|
||||||
def _get_selected_row(self) -> Optional[int]:
|
|
||||||
"""
|
|
||||||
Return row_number number of first selected row,
|
|
||||||
or None if none selected
|
|
||||||
"""
|
|
||||||
|
|
||||||
sm = self.selectionModel()
|
|
||||||
if sm:
|
|
||||||
if sm.hasSelection():
|
|
||||||
return sm.selectedIndexes()[0].row()
|
|
||||||
return None
|
|
||||||
|
|
||||||
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 sorted by row"""
|
||||||
|
|
||||||
@ -739,6 +729,13 @@ class PlaylistTab(QTableView):
|
|||||||
# else:
|
# else:
|
||||||
# return
|
# return
|
||||||
|
|
||||||
|
def _mark_as_unplayed(self, row_numbers: List[int]) -> None:
|
||||||
|
"""Rescan track"""
|
||||||
|
|
||||||
|
model = cast(PlaylistModel, self.model())
|
||||||
|
model.mark_unplayed(row_numbers)
|
||||||
|
self.clear_selection()
|
||||||
|
|
||||||
def _obs_change_scene(self, current_row: int) -> None:
|
def _obs_change_scene(self, current_row: int) -> None:
|
||||||
"""
|
"""
|
||||||
Try to change OBS scene to the name passed
|
Try to change OBS scene to the name passed
|
||||||
@ -1017,3 +1014,10 @@ class PlaylistTab(QTableView):
|
|||||||
return
|
return
|
||||||
|
|
||||||
self.setSpan(row, column, rowSpan, columnSpan)
|
self.setSpan(row, column, rowSpan, columnSpan)
|
||||||
|
|
||||||
|
def _unmark_as_next(self) -> None:
|
||||||
|
"""Rescan track"""
|
||||||
|
|
||||||
|
model = cast(PlaylistModel, self.model())
|
||||||
|
model.set_next_row(None)
|
||||||
|
self.clear_selection()
|
||||||
|
|||||||
@ -75,7 +75,13 @@ def main():
|
|||||||
continue
|
continue
|
||||||
new_tags = get_tags(new_path)
|
new_tags = get_tags(new_path)
|
||||||
new_title = new_tags["title"]
|
new_title = new_tags["title"]
|
||||||
|
if not new_title:
|
||||||
|
print(f"{new_fname} does not have a title tag")
|
||||||
|
sys.exit(1)
|
||||||
new_artist = new_tags["artist"]
|
new_artist = new_tags["artist"]
|
||||||
|
if not new_artist:
|
||||||
|
print(f"{new_fname} does not have an artist tag")
|
||||||
|
sys.exit(1)
|
||||||
bitrate = new_tags["bitrate"]
|
bitrate = new_tags["bitrate"]
|
||||||
|
|
||||||
# If same filename exists in parent direcory, check tags
|
# If same filename exists in parent direcory, check tags
|
||||||
|
|||||||
59
poetry.lock
generated
59
poetry.lock
generated
@ -859,39 +859,39 @@ files = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mypy"
|
name = "mypy"
|
||||||
version = "1.6.1"
|
version = "1.7.0"
|
||||||
description = "Optional static typing for Python"
|
description = "Optional static typing for Python"
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "mypy-1.6.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e5012e5cc2ac628177eaac0e83d622b2dd499e28253d4107a08ecc59ede3fc2c"},
|
{file = "mypy-1.7.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5da84d7bf257fd8f66b4f759a904fd2c5a765f70d8b52dde62b521972a0a2357"},
|
||||||
{file = "mypy-1.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d8fbb68711905f8912e5af474ca8b78d077447d8f3918997fecbf26943ff3cbb"},
|
{file = "mypy-1.7.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a3637c03f4025f6405737570d6cbfa4f1400eb3c649317634d273687a09ffc2f"},
|
||||||
{file = "mypy-1.6.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21a1ad938fee7d2d96ca666c77b7c494c3c5bd88dff792220e1afbebb2925b5e"},
|
{file = "mypy-1.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b633f188fc5ae1b6edca39dae566974d7ef4e9aaaae00bc36efe1f855e5173ac"},
|
||||||
{file = "mypy-1.6.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b96ae2c1279d1065413965c607712006205a9ac541895004a1e0d4f281f2ff9f"},
|
{file = "mypy-1.7.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d6ed9a3997b90c6f891138e3f83fb8f475c74db4ccaa942a1c7bf99e83a989a1"},
|
||||||
{file = "mypy-1.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:40b1844d2e8b232ed92e50a4bd11c48d2daa351f9deee6c194b83bf03e418b0c"},
|
{file = "mypy-1.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:1fe46e96ae319df21359c8db77e1aecac8e5949da4773c0274c0ef3d8d1268a9"},
|
||||||
{file = "mypy-1.6.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:81af8adaa5e3099469e7623436881eff6b3b06db5ef75e6f5b6d4871263547e5"},
|
{file = "mypy-1.7.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:df67fbeb666ee8828f675fee724cc2cbd2e4828cc3df56703e02fe6a421b7401"},
|
||||||
{file = "mypy-1.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8c223fa57cb154c7eab5156856c231c3f5eace1e0bed9b32a24696b7ba3c3245"},
|
{file = "mypy-1.7.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a79cdc12a02eb526d808a32a934c6fe6df07b05f3573d210e41808020aed8b5d"},
|
||||||
{file = "mypy-1.6.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8032e00ce71c3ceb93eeba63963b864bf635a18f6c0c12da6c13c450eedb183"},
|
{file = "mypy-1.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f65f385a6f43211effe8c682e8ec3f55d79391f70a201575def73d08db68ead1"},
|
||||||
{file = "mypy-1.6.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:4c46b51de523817a0045b150ed11b56f9fff55f12b9edd0f3ed35b15a2809de0"},
|
{file = "mypy-1.7.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0e81ffd120ee24959b449b647c4b2fbfcf8acf3465e082b8d58fd6c4c2b27e46"},
|
||||||
{file = "mypy-1.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:19f905bcfd9e167159b3d63ecd8cb5e696151c3e59a1742e79bc3bcb540c42c7"},
|
{file = "mypy-1.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:f29386804c3577c83d76520abf18cfcd7d68264c7e431c5907d250ab502658ee"},
|
||||||
{file = "mypy-1.6.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:82e469518d3e9a321912955cc702d418773a2fd1e91c651280a1bda10622f02f"},
|
{file = "mypy-1.7.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:87c076c174e2c7ef8ab416c4e252d94c08cd4980a10967754f91571070bf5fbe"},
|
||||||
{file = "mypy-1.6.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d4473c22cc296425bbbce7e9429588e76e05bc7342da359d6520b6427bf76660"},
|
{file = "mypy-1.7.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6cb8d5f6d0fcd9e708bb190b224089e45902cacef6f6915481806b0c77f7786d"},
|
||||||
{file = "mypy-1.6.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59a0d7d24dfb26729e0a068639a6ce3500e31d6655df8557156c51c1cb874ce7"},
|
{file = "mypy-1.7.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d93e76c2256aa50d9c82a88e2f569232e9862c9982095f6d54e13509f01222fc"},
|
||||||
{file = "mypy-1.6.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:cfd13d47b29ed3bbaafaff7d8b21e90d827631afda134836962011acb5904b71"},
|
{file = "mypy-1.7.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:cddee95dea7990e2215576fae95f6b78a8c12f4c089d7e4367564704e99118d3"},
|
||||||
{file = "mypy-1.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:eb4f18589d196a4cbe5290b435d135dee96567e07c2b2d43b5c4621b6501531a"},
|
{file = "mypy-1.7.0-cp312-cp312-win_amd64.whl", hash = "sha256:d01921dbd691c4061a3e2ecdbfbfad029410c5c2b1ee88946bf45c62c6c91210"},
|
||||||
{file = "mypy-1.6.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:41697773aa0bf53ff917aa077e2cde7aa50254f28750f9b88884acea38a16169"},
|
{file = "mypy-1.7.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:185cff9b9a7fec1f9f7d8352dff8a4c713b2e3eea9c6c4b5ff7f0edf46b91e41"},
|
||||||
{file = "mypy-1.6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7274b0c57737bd3476d2229c6389b2ec9eefeb090bbaf77777e9d6b1b5a9d143"},
|
{file = "mypy-1.7.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7a7b1e399c47b18feb6f8ad4a3eef3813e28c1e871ea7d4ea5d444b2ac03c418"},
|
||||||
{file = "mypy-1.6.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbaf4662e498c8c2e352da5f5bca5ab29d378895fa2d980630656178bd607c46"},
|
{file = "mypy-1.7.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fc9fe455ad58a20ec68599139ed1113b21f977b536a91b42bef3ffed5cce7391"},
|
||||||
{file = "mypy-1.6.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bb8ccb4724f7d8601938571bf3f24da0da791fe2db7be3d9e79849cb64e0ae85"},
|
{file = "mypy-1.7.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d0fa29919d2e720c8dbaf07d5578f93d7b313c3e9954c8ec05b6d83da592e5d9"},
|
||||||
{file = "mypy-1.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:68351911e85145f582b5aa6cd9ad666c8958bcae897a1bfda8f4940472463c45"},
|
{file = "mypy-1.7.0-cp38-cp38-win_amd64.whl", hash = "sha256:2b53655a295c1ed1af9e96b462a736bf083adba7b314ae775563e3fb4e6795f5"},
|
||||||
{file = "mypy-1.6.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:49ae115da099dcc0922a7a895c1eec82c1518109ea5c162ed50e3b3594c71208"},
|
{file = "mypy-1.7.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c1b06b4b109e342f7dccc9efda965fc3970a604db70f8560ddfdee7ef19afb05"},
|
||||||
{file = "mypy-1.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8b27958f8c76bed8edaa63da0739d76e4e9ad4ed325c814f9b3851425582a3cd"},
|
{file = "mypy-1.7.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bf7a2f0a6907f231d5e41adba1a82d7d88cf1f61a70335889412dec99feeb0f8"},
|
||||||
{file = "mypy-1.6.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:925cd6a3b7b55dfba252b7c4561892311c5358c6b5a601847015a1ad4eb7d332"},
|
{file = "mypy-1.7.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:551d4a0cdcbd1d2cccdcc7cb516bb4ae888794929f5b040bb51aae1846062901"},
|
||||||
{file = "mypy-1.6.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8f57e6b6927a49550da3d122f0cb983d400f843a8a82e65b3b380d3d7259468f"},
|
{file = "mypy-1.7.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:55d28d7963bef00c330cb6461db80b0b72afe2f3c4e2963c99517cf06454e665"},
|
||||||
{file = "mypy-1.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:a43ef1c8ddfdb9575691720b6352761f3f53d85f1b57d7745701041053deff30"},
|
{file = "mypy-1.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:870bd1ffc8a5862e593185a4c169804f2744112b4a7c55b93eb50f48e7a77010"},
|
||||||
{file = "mypy-1.6.1-py3-none-any.whl", hash = "sha256:4cbe68ef919c28ea561165206a2dcb68591c50f3bcf777932323bc208d949cf1"},
|
{file = "mypy-1.7.0-py3-none-any.whl", hash = "sha256:96650d9a4c651bc2a4991cf46f100973f656d69edc7faf91844e87fe627f7e96"},
|
||||||
{file = "mypy-1.6.1.tar.gz", hash = "sha256:4d01c00d09a0be62a4ca3f933e315455bde83f37f892ba4b08ce92f3cf44bcc1"},
|
{file = "mypy-1.7.0.tar.gz", hash = "sha256:1e280b5697202efa698372d2f39e9a6713a0395a756b1c6bd48995f8d72690dc"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
@ -902,6 +902,7 @@ typing-extensions = ">=4.1.0"
|
|||||||
[package.extras]
|
[package.extras]
|
||||||
dmypy = ["psutil (>=4.0)"]
|
dmypy = ["psutil (>=4.0)"]
|
||||||
install-types = ["pip"]
|
install-types = ["pip"]
|
||||||
|
mypyc = ["setuptools (>=50)"]
|
||||||
reports = ["lxml"]
|
reports = ["lxml"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2188,4 +2189,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = "^3.9"
|
python-versions = "^3.9"
|
||||||
content-hash = "c822ba4ac16ffd05a4bfbcdb552aacc44e75d62deb4cfdba16771a8b13e14187"
|
content-hash = "5bd0a9ae09f61079a0325639485adb206357cd5ea942944ccb5855f2a83d4db6"
|
||||||
|
|||||||
@ -41,7 +41,7 @@ sphinx = "^7.0.1"
|
|||||||
furo = "^2023.5.20"
|
furo = "^2023.5.20"
|
||||||
black = "^23.3.0"
|
black = "^23.3.0"
|
||||||
flakehell = "^0.9.0"
|
flakehell = "^0.9.0"
|
||||||
mypy = "^1.6.0"
|
mypy = "^1.7.0"
|
||||||
pdbp = "^1.5.0"
|
pdbp = "^1.5.0"
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user