table editing signal emission
This commit is contained in:
parent
6daadb09aa
commit
ef02d30d05
@ -1,7 +1,7 @@
|
||||
import DBA
|
||||
from PyQt5.QtGui import QStandardItem, QStandardItemModel, QKeySequence
|
||||
from PyQt5.QtWidgets import QTableView, QShortcut
|
||||
from PyQt5.QtCore import Qt, pyqtSignal
|
||||
from PyQt5.QtGui import QStandardItem, QStandardItemModel, QKeySequence
|
||||
from PyQt5.QtWidgets import QTableView, QShortcut, QMessageBox, QAbstractItemView
|
||||
from PyQt5.QtCore import QModelIndex, Qt, pyqtSignal
|
||||
from utils import add_files_to_library
|
||||
from utils import get_id3_tags
|
||||
from utils import get_album_art
|
||||
@ -12,15 +12,18 @@ import configparser
|
||||
class MusicTable(QTableView):
|
||||
playPauseSignal = pyqtSignal()
|
||||
enterKey = pyqtSignal()
|
||||
def __init__(self, parent=None, qapp=None):
|
||||
QTableView.__init__(self, parent)
|
||||
self.headers = ['title', 'artist', 'album', 'genre', 'codec', 'year', 'path']
|
||||
def __init__(self, parent=None):
|
||||
# QTableView.__init__(self, parent)
|
||||
super().__init__(parent)
|
||||
self.model = QStandardItemModel(self) # Necessary for actions related to cell values
|
||||
self.setModel(self.model) # Same as above
|
||||
self.config = configparser.ConfigParser()
|
||||
self.config.read('config.ini')
|
||||
self.headers = ['title', 'artist', 'album', 'genre', 'codec', 'year', 'path'] # gui names of headers
|
||||
self.columns = str(self.config['table']['columns']).split(',') # db names of headers
|
||||
self.songChanged = None
|
||||
self.selected_song_filepath = None
|
||||
self.current_song_filepath = None
|
||||
self.qapp = None
|
||||
self.config = configparser.ConfigParser()
|
||||
self.config.read('config.ini')
|
||||
# self.tableView.resizeColumnsToContents()
|
||||
self.clicked.connect(self.set_selected_song_filepath)
|
||||
# doubleClicked is a built in event for QTableView - we listen for this event and run set_current_song_filepath
|
||||
@ -28,23 +31,43 @@ class MusicTable(QTableView):
|
||||
self.enterKey.connect(self.set_current_song_filepath)
|
||||
self.fetch_library()
|
||||
self.setup_keyboard_shortcuts()
|
||||
self.model.dataChanged.connect(self.on_cell_data_changed) # editing cells
|
||||
|
||||
|
||||
def setup_keyboard_shortcuts(self):
|
||||
"""Setup shortcuts here"""
|
||||
shortcut = QShortcut(QKeySequence("Ctrl+Shift+R"), self)
|
||||
shortcut.activated.connect(self.reorganize_selected_files)
|
||||
|
||||
|
||||
def on_cell_data_changed(self, topLeft: QModelIndex, bottomRight: QModelIndex):
|
||||
"""Handles updating ID3 tags when data changes in a cell"""
|
||||
print('on_cell_data_changed')
|
||||
filepath_column_idx = self.model.columnCount() - 1 # always the last column
|
||||
filepath_index = self.model.index(topLeft.row(), filepath_column_idx) # exact index of the edited cell
|
||||
filepath = self.model.data(filepath_index) # filepath
|
||||
# update the ID3 information
|
||||
new_data = topLeft.data()
|
||||
edited_column = self.columns[topLeft.column()]
|
||||
print(filepath)
|
||||
print(edited_column)
|
||||
|
||||
|
||||
def reorganize_selected_files(self):
|
||||
"""Ctrl+Shift+R = Reorganize"""
|
||||
filepaths = self.get_selected_songs_filepaths()
|
||||
# right now this just prints the filepaths to the songs obv.
|
||||
print(f'yay: {filepaths}')
|
||||
# TODO
|
||||
# Get user confirmation screen (default yes, so i can press enter and go)
|
||||
# Get target directory
|
||||
# Copy files to new dir
|
||||
# delete files from current dir
|
||||
# add new files to library
|
||||
# Confirmation screen (yes, no)
|
||||
reply = QMessageBox.question(self, 'Confirmation', 'Are you sure you want to reorganize these files?', QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes)
|
||||
if reply:
|
||||
target_dir = str(self.config['directories']['reorganize_destination'])
|
||||
print(target_dir)
|
||||
|
||||
# Get target directory
|
||||
# Copy files to new dir
|
||||
# delete files from current dir
|
||||
# add new files to library
|
||||
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
"""Press a key. Do a thing"""
|
||||
@ -62,21 +85,27 @@ class MusicTable(QTableView):
|
||||
if new_index.isValid():
|
||||
self.setCurrentIndex(new_index)
|
||||
elif key in (Qt.Key_Return, Qt.Key_Enter):
|
||||
self.enterKey.emit() # Enter key detected
|
||||
if self.state() != QAbstractItemView.EditingState:
|
||||
self.enterKey.emit() # Enter key detected
|
||||
else:
|
||||
super().keyPressEvent(event)
|
||||
else: # Default behavior
|
||||
super().keyPressEvent(event)
|
||||
|
||||
|
||||
def toggle_play_pause(self):
|
||||
"""Toggles the currently playing song by emitting a signal"""
|
||||
if not self.current_song_filepath:
|
||||
self.set_current_song_filepath()
|
||||
self.playPauseSignal.emit()
|
||||
|
||||
|
||||
def set_selected_song_filepath(self):
|
||||
"""Sets the filepath of the currently selected song"""
|
||||
self.selected_song_filepath = self.currentIndex().siblingAtColumn(self.headers.index('path')).data()
|
||||
print(f'Selected song: {self.selected_song_filepath}')
|
||||
|
||||
|
||||
def set_current_song_filepath(self):
|
||||
"""Sets the filepath of the currently playing song"""
|
||||
# Setting the current song filepath automatically plays that song
|
||||
@ -84,6 +113,7 @@ class MusicTable(QTableView):
|
||||
self.current_song_filepath = self.currentIndex().siblingAtColumn(self.headers.index('path')).data()
|
||||
print(f'Current song: {self.current_song_filepath}')
|
||||
|
||||
|
||||
def get_selected_rows(self):
|
||||
"""Returns a list of indexes for every selected row"""
|
||||
selection_model = self.selectionModel()
|
||||
@ -93,6 +123,7 @@ class MusicTable(QTableView):
|
||||
# rows.append(idx.row())
|
||||
# return rows
|
||||
|
||||
|
||||
def get_selected_songs_filepaths(self):
|
||||
"""Returns a list of the filepaths for the currently selected songs"""
|
||||
selected_rows = self.get_selected_rows()
|
||||
@ -102,29 +133,34 @@ class MusicTable(QTableView):
|
||||
filepaths.append(idx.data())
|
||||
return filepaths
|
||||
|
||||
|
||||
def get_selected_song_filepath(self):
|
||||
"""Returns the selected songs filepath"""
|
||||
return self.selected_song_filepath
|
||||
|
||||
|
||||
def get_selected_song_metadata(self):
|
||||
"""Returns the selected song's ID3 tags"""
|
||||
return get_id3_tags(self.selected_song_filepath)
|
||||
|
||||
|
||||
def get_current_song_filepath(self):
|
||||
"""Returns the currently playing song filepath"""
|
||||
return self.current_song_filepath
|
||||
|
||||
|
||||
def get_current_song_metadata(self):
|
||||
"""Returns the currently playing song's ID3 tags"""
|
||||
return get_id3_tags(self.current_song_filepath)
|
||||
|
||||
|
||||
def get_current_song_album_art(self):
|
||||
"""Returns the APIC data (album art lol) for the currently playing song"""
|
||||
return get_album_art(self.current_song_filepath)
|
||||
|
||||
|
||||
def fetch_library(self):
|
||||
"""Initialize the tableview model"""
|
||||
self.model = QStandardItemModel()
|
||||
self.model.setHorizontalHeaderLabels(self.headers)
|
||||
# Fetch library data
|
||||
with DBA.DBAccess() as db:
|
||||
@ -133,11 +169,10 @@ class MusicTable(QTableView):
|
||||
for row_data in data:
|
||||
items = [QStandardItem(str(item)) for item in row_data]
|
||||
self.model.appendRow(items)
|
||||
# Set the model to the tableView (we are the tableview)
|
||||
self.setModel(self.model)
|
||||
# self.update()
|
||||
# Update the viewport/model
|
||||
self.viewport().update()
|
||||
|
||||
|
||||
def add_files(self, files):
|
||||
"""When song(s) added to the library, update the tableview model
|
||||
- Drag & Drop song(s) on tableView
|
||||
@ -148,6 +183,7 @@ class MusicTable(QTableView):
|
||||
if number_of_files_added:
|
||||
self.fetch_library()
|
||||
|
||||
|
||||
def load_qapp(self, qapp):
|
||||
# why was this necessary again? :thinking:
|
||||
self.qapp = qapp
|
||||
|
||||
19
main.py
19
main.py
@ -2,9 +2,9 @@ from ui import Ui_MainWindow
|
||||
from PyQt5.QtWidgets import QMainWindow, QApplication, QGraphicsScene, \
|
||||
QHeaderView, QGraphicsPixmapItem
|
||||
import qdarktheme
|
||||
from PyQt5.QtCore import QUrl, QTimer, QEvent, Qt
|
||||
from PyQt5.QtCore import QUrl, QTimer, QEvent, Qt, QModelIndex
|
||||
from PyQt5.QtMultimedia import QMediaPlayer, QMediaContent, QAudioProbe
|
||||
from PyQt5.QtGui import QPixmap
|
||||
from PyQt5.QtGui import QPixmap, QStandardItemModel
|
||||
from utils import scan_for_music
|
||||
from utils import initialize_library_database
|
||||
from components import AudioVisualizer
|
||||
@ -27,17 +27,18 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
|
||||
self.current_song_album_art = None
|
||||
self.album_art_scene = QGraphicsScene()
|
||||
self.qapp = qapp
|
||||
print(f'ApplicationWindow self.qapp: {self.qapp}')
|
||||
# print(f'ApplicationWindow self.qapp: {self.qapp}')
|
||||
self.tableView.load_qapp(self.qapp)
|
||||
# print(f'tableView type: {type(self.tableView)}')
|
||||
self.config = configparser.ConfigParser()
|
||||
self.config.read('config.ini')
|
||||
global stopped
|
||||
stopped = False
|
||||
# Initialization
|
||||
self.player = QMediaPlayer() # Audio player
|
||||
self.probe = QAudioProbe() # Get audio data
|
||||
self.player = QMediaPlayer() # Audio player object
|
||||
self.probe = QAudioProbe() # Gets audio data
|
||||
self.timer = QTimer(self) # Audio timing things
|
||||
# self.model = QStandardItemModel() # Table library listing
|
||||
# self.music_table_model = QStandardItemModel(self) # Table library listing
|
||||
self.audio_visualizer = AudioVisualizer(self.player)
|
||||
self.current_volume = 50
|
||||
self.player.setVolume(self.current_volume)
|
||||
@ -60,25 +61,21 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
|
||||
self.PlotWidget.getAxis('left').setTicks([]) # Remove y-axis ticks
|
||||
self.PlotWidget.getAxis('left').setLabel('') # Remove y-axis label
|
||||
|
||||
|
||||
# Connections
|
||||
# ! FIXME moving the slider while playing is happening frequently causes playback to halt - the pyqtgraph is also affected by this
|
||||
self.playbackSlider.sliderMoved[int].connect(lambda: self.player.setPosition(self.playbackSlider.value())) # Move slidet to adjust playback time
|
||||
self.volumeSlider.sliderMoved[int].connect(lambda: self.volume_changed()) # Move slider to adjust volume
|
||||
self.playButton.clicked.connect(self.on_play_clicked) # Click to play/pause
|
||||
# self.pauseButton.clicked.connect(self.on_pause_clicked)
|
||||
self.previousButton.clicked.connect(self.on_previous_clicked) # Click to previous song
|
||||
self.nextButton.clicked.connect(self.on_next_clicked) # Click to next song
|
||||
self.actionPreferences.triggered.connect(self.actionPreferencesClicked) # Open preferences menu
|
||||
self.actionScanLibraries.triggered.connect(self.scan_libraries) # Scan library
|
||||
self.actionClearDatabase.triggered.connect(self.clear_database) # Clear database
|
||||
## tableView
|
||||
# self.tableView.clicked.connect(self.set_clicked_cell_filepath)
|
||||
self.tableView.doubleClicked.connect(self.play_audio_file) # Listens for the double click event, then plays the song
|
||||
self.tableView.enterKey.connect(self.play_audio_file) # Listens for the enter key event, then plays the song
|
||||
self.tableView.playPauseSignal.connect(self.on_play_clicked) # Spacebar toggle play/pause signal
|
||||
self.tableView.viewport().installEventFilter(self) # for drag & drop functionality
|
||||
# self.tableView.model.layoutChanged()
|
||||
# set column widths
|
||||
table_view_column_widths = str(self.config['table']['column_widths']).split(',')
|
||||
for i in range(self.tableView.model.columnCount()):
|
||||
@ -119,6 +116,7 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
|
||||
self.config.write(configfile)
|
||||
super().closeEvent(event)
|
||||
|
||||
|
||||
def play_audio_file(self):
|
||||
"""Start playback of tableView.current_song_filepath track & moves playback slider"""
|
||||
self.current_song_metadata = self.tableView.get_current_song_metadata() # get metadata
|
||||
@ -141,7 +139,6 @@ class ApplicationWindow(QMainWindow, Ui_MainWindow):
|
||||
# set album artwork
|
||||
self.load_album_art(self.current_song_album_art)
|
||||
|
||||
|
||||
def load_album_art(self, album_art_data):
|
||||
"""Sets the album art for the currently playing track"""
|
||||
if self.current_song_album_art:
|
||||
|
||||
@ -12,5 +12,6 @@ reorganize_destination = /where/to/reorganize/to
|
||||
extensions = mp3,wav,ogg,flac
|
||||
|
||||
[table]
|
||||
# Music table column widths
|
||||
# Music table options
|
||||
columns = title,artist,album,genre,codec,album_date,filepath
|
||||
column_widths = 181,116,222,76,74,72,287
|
||||
@ -9,14 +9,12 @@ config.read("config.ini")
|
||||
|
||||
def add_files_to_library(files):
|
||||
"""Adds audio file(s) to the sqllite db
|
||||
|
||||
files | list() of fully qualified paths to audio file(s)
|
||||
|
||||
files = list() of fully qualified paths to audio file(s)
|
||||
Returns true if any files were added
|
||||
"""
|
||||
if not files:
|
||||
return False
|
||||
print(f"utils | adding files to library: {files}")
|
||||
print(f"utils/add_files_to_library: {files}")
|
||||
extensions = config.get("settings", "extensions").split(",")
|
||||
insert_data = [] # To store data for batch insert
|
||||
for filepath in files:
|
||||
|
||||
@ -1,18 +1,27 @@
|
||||
from mutagen.easyid3 import EasyID3
|
||||
from mutagen import File
|
||||
|
||||
|
||||
def get_id3_tags(file):
|
||||
"""Get the ID3 tags for an audio file
|
||||
# Parameters
|
||||
`file` | str | Fully qualified path to file
|
||||
|
||||
# Returns
|
||||
dict of all id3 tags
|
||||
if all tags are empty, at minimum fill in the 'title'
|
||||
"""
|
||||
try:
|
||||
audio = EasyID3(file)
|
||||
print(f'ID3 Tags: {audio}')
|
||||
# Check if all tags are empty
|
||||
tags_are_empty = all(not values for values in audio.values())
|
||||
if tags_are_empty:
|
||||
audio['title'] = [file.split('/')[-1]]
|
||||
return audio
|
||||
except Exception as e:
|
||||
print(f"Error: {e}")
|
||||
return {}
|
||||
|
||||
# import sys
|
||||
# my_file = sys.argv[1]
|
||||
# data = get_id3_tags(my_file)
|
||||
# print(data)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user