WIP V3: import tracks working

This commit is contained in:
Keith Edmunds 2023-11-19 16:02:44 +00:00
parent 005d17ee0a
commit db547cbdb7
3 changed files with 110 additions and 71 deletions

View File

@ -84,6 +84,8 @@ class MusicMusterSignals(QObject):
enable_escape_signal = pyqtSignal(bool) enable_escape_signal = pyqtSignal(bool)
next_track_changed_signal = pyqtSignal() next_track_changed_signal = pyqtSignal()
span_cells_signal = pyqtSignal(int, int, int, int) span_cells_signal = pyqtSignal(int, int, int, int)
show_warning_signal = pyqtSignal(str, str)
status_message_signal = pyqtSignal(str, int)
def __post_init__(self): def __post_init__(self):
super().__init__() super().__init__()

View File

@ -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,
@ -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
@ -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"""
@ -1355,6 +1307,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 +1324,14 @@ 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
"""
print(f"show_status_message({message=}, {timing=})")
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]:

View File

@ -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
@ -1079,3 +1109,42 @@ 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()