WIP V3: import tracks working
This commit is contained in:
parent
005d17ee0a
commit
db547cbdb7
@ -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__()
|
||||||
|
|||||||
@ -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]:
|
||||||
|
|||||||
@ -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()
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user