light changes
This commit is contained in:
parent
55dfc36bf0
commit
04201fcb1d
@ -27,7 +27,8 @@ python3 main.py
|
|||||||
|
|
||||||
## Todo:
|
## Todo:
|
||||||
|
|
||||||
- [ ] Delete songs from library (del key || right-click delete)
|
|
||||||
- [x] Right-click menu
|
- [x] Right-click menu
|
||||||
|
- [x] Editable lyrics textbox
|
||||||
|
- [ ] Delete songs from library (del key || right-click delete)
|
||||||
- [ ] .wav, .ogg, .flac convertor
|
- [ ] .wav, .ogg, .flac convertor
|
||||||
- [ ] Editable lyrics textbox
|
- [ ] Alternatives to Gstreamer?
|
||||||
|
|||||||
@ -16,16 +16,14 @@ from PyQt5.QtWidgets import (
|
|||||||
QMessageBox,
|
QMessageBox,
|
||||||
QAbstractItemView,
|
QAbstractItemView,
|
||||||
)
|
)
|
||||||
from PyQt5.QtCore import QModelIndex, Qt, pyqtSignal, QTimer
|
from PyQt5.QtCore import QAbstractItemModel, QModelIndex, Qt, pyqtSignal, QTimer
|
||||||
from components.LyricsWindow import LyricsWindow
|
from components.LyricsWindow import LyricsWindow
|
||||||
from utils import add_files_to_library
|
from utils import add_files_to_library
|
||||||
from utils import update_song_in_library
|
from utils import update_song_in_library
|
||||||
from utils import get_id3_tags
|
from utils import get_id3_tags
|
||||||
from utils import get_album_art
|
from utils import get_album_art
|
||||||
from utils import set_id3_tag
|
from utils import set_id3_tag
|
||||||
from utils import delete_and_create_library_database
|
|
||||||
from subprocess import Popen
|
from subprocess import Popen
|
||||||
from sqlite3 import OperationalError
|
|
||||||
import logging
|
import logging
|
||||||
import configparser
|
import configparser
|
||||||
import os
|
import os
|
||||||
@ -35,12 +33,13 @@ import shutil
|
|||||||
class MusicTable(QTableView):
|
class MusicTable(QTableView):
|
||||||
playPauseSignal = pyqtSignal()
|
playPauseSignal = pyqtSignal()
|
||||||
enterKey = pyqtSignal()
|
enterKey = pyqtSignal()
|
||||||
|
deleteKey = pyqtSignal()
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self: QTableView, parent=None):
|
||||||
# QTableView.__init__(self, parent)
|
# QTableView.__init__(self, parent)
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.model = QStandardItemModel(self)
|
|
||||||
# Necessary for actions related to cell values
|
# Necessary for actions related to cell values
|
||||||
|
self.model = QStandardItemModel(self)
|
||||||
self.setModel(self.model) # Same as above
|
self.setModel(self.model) # Same as above
|
||||||
self.config = configparser.ConfigParser()
|
self.config = configparser.ConfigParser()
|
||||||
self.config.read("config.ini")
|
self.config.read("config.ini")
|
||||||
@ -56,9 +55,9 @@ class MusicTable(QTableView):
|
|||||||
]
|
]
|
||||||
# id3 names of headers
|
# id3 names of headers
|
||||||
self.id3_headers = [
|
self.id3_headers = [
|
||||||
"title",
|
"TIT2",
|
||||||
"artist",
|
"TPE1",
|
||||||
"album",
|
"TALB",
|
||||||
"content_type",
|
"content_type",
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
@ -75,6 +74,7 @@ class MusicTable(QTableView):
|
|||||||
# doubleClicked is a built in event for QTableView - we listen for this event and run set_current_song_filepath
|
# doubleClicked is a built in event for QTableView - we listen for this event and run set_current_song_filepath
|
||||||
self.doubleClicked.connect(self.set_current_song_filepath)
|
self.doubleClicked.connect(self.set_current_song_filepath)
|
||||||
self.enterKey.connect(self.set_current_song_filepath)
|
self.enterKey.connect(self.set_current_song_filepath)
|
||||||
|
self.deleteKey.connect(self.delete_songs)
|
||||||
self.fetch_library()
|
self.fetch_library()
|
||||||
self.setup_keyboard_shortcuts()
|
self.setup_keyboard_shortcuts()
|
||||||
self.model.dataChanged.connect(self.on_cell_data_changed) # editing cells
|
self.model.dataChanged.connect(self.on_cell_data_changed) # editing cells
|
||||||
@ -104,7 +104,7 @@ class MusicTable(QTableView):
|
|||||||
reply = QMessageBox.question(
|
reply = QMessageBox.question(
|
||||||
self,
|
self,
|
||||||
"Confirmation",
|
"Confirmation",
|
||||||
"Are you sure you want to delete these songs?",
|
"Remove these songs from the library? (Files stay on your computer)",
|
||||||
QMessageBox.Yes | QMessageBox.No,
|
QMessageBox.Yes | QMessageBox.No,
|
||||||
QMessageBox.Yes,
|
QMessageBox.Yes,
|
||||||
)
|
)
|
||||||
@ -139,11 +139,11 @@ class MusicTable(QTableView):
|
|||||||
if selected_song_filepath is None:
|
if selected_song_filepath is None:
|
||||||
return
|
return
|
||||||
current_song = self.get_selected_song_metadata()
|
current_song = self.get_selected_song_metadata()
|
||||||
print(f"MusicTable.py | show_lyrics_menu | current song: {current_song}")
|
# print(f"MusicTable.py | show_lyrics_menu | current song: {current_song}")
|
||||||
try:
|
try:
|
||||||
lyrics = current_song["USLT::XXX"].text
|
lyrics = current_song["USLT::XXX"].text
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f'MusicTable.py | show_lyrics_menu | could not retrieve lyrics | {e}')
|
print(f"MusicTable.py | show_lyrics_menu | could not retrieve lyrics | {e}")
|
||||||
lyrics = ""
|
lyrics = ""
|
||||||
lyrics_window = LyricsWindow(selected_song_filepath, lyrics)
|
lyrics_window = LyricsWindow(selected_song_filepath, lyrics)
|
||||||
lyrics_window.exec_()
|
lyrics_window.exec_()
|
||||||
@ -180,6 +180,33 @@ class MusicTable(QTableView):
|
|||||||
else:
|
else:
|
||||||
e.ignore()
|
e.ignore()
|
||||||
|
|
||||||
|
def keyPressEvent(self, event):
|
||||||
|
"""Press a key. Do a thing"""
|
||||||
|
key = event.key()
|
||||||
|
if key == Qt.Key_Space: # Spacebar to play/pause
|
||||||
|
self.toggle_play_pause()
|
||||||
|
elif key == Qt.Key_Up: # Arrow key navigation
|
||||||
|
current_index = self.currentIndex()
|
||||||
|
new_index = self.model.index(
|
||||||
|
current_index.row() - 1, current_index.column()
|
||||||
|
)
|
||||||
|
if new_index.isValid():
|
||||||
|
self.setCurrentIndex(new_index)
|
||||||
|
elif key == Qt.Key_Down: # Arrow key navigation
|
||||||
|
current_index = self.currentIndex()
|
||||||
|
new_index = self.model.index(
|
||||||
|
current_index.row() + 1, current_index.column()
|
||||||
|
)
|
||||||
|
if new_index.isValid():
|
||||||
|
self.setCurrentIndex(new_index)
|
||||||
|
elif key in (Qt.Key_Return, Qt.Key_Enter):
|
||||||
|
if self.state() != QAbstractItemView.EditingState:
|
||||||
|
self.enterKey.emit() # Enter key detected
|
||||||
|
else:
|
||||||
|
super().keyPressEvent(event)
|
||||||
|
else: # Default behavior
|
||||||
|
super().keyPressEvent(event)
|
||||||
|
|
||||||
def setup_keyboard_shortcuts(self):
|
def setup_keyboard_shortcuts(self):
|
||||||
"""Setup shortcuts here"""
|
"""Setup shortcuts here"""
|
||||||
shortcut = QShortcut(QKeySequence("Ctrl+Shift+R"), self)
|
shortcut = QShortcut(QKeySequence("Ctrl+Shift+R"), self)
|
||||||
@ -222,8 +249,10 @@ class MusicTable(QTableView):
|
|||||||
try:
|
try:
|
||||||
# Read file metadata
|
# Read file metadata
|
||||||
audio = ID3(filepath)
|
audio = ID3(filepath)
|
||||||
artist = audio["TIT2"].text[0] if not '' or None else 'Unknown Artist'
|
artist = (
|
||||||
album = audio["TALB"].text[0] if not '' or None else 'Unknown Album'
|
audio["TIT2"].text[0] if not "" or None else "Unknown Artist"
|
||||||
|
)
|
||||||
|
album = audio["TALB"].text[0] if not "" or None else "Unknown Album"
|
||||||
# Determine the new path that needs to be made
|
# Determine the new path that needs to be made
|
||||||
new_path = os.path.join(
|
new_path = os.path.join(
|
||||||
target_dir, artist, album, os.path.basename(filepath)
|
target_dir, artist, album, os.path.basename(filepath)
|
||||||
@ -249,33 +278,6 @@ class MusicTable(QTableView):
|
|||||||
self, "Reorganization complete", "Files successfully reorganized"
|
self, "Reorganization complete", "Files successfully reorganized"
|
||||||
)
|
)
|
||||||
|
|
||||||
def keyPressEvent(self, event):
|
|
||||||
"""Press a key. Do a thing"""
|
|
||||||
key = event.key()
|
|
||||||
if key == Qt.Key_Space: # Spacebar to play/pause
|
|
||||||
self.toggle_play_pause()
|
|
||||||
elif key == Qt.Key_Up: # Arrow key navigation
|
|
||||||
current_index = self.currentIndex()
|
|
||||||
new_index = self.model.index(
|
|
||||||
current_index.row() - 1, current_index.column()
|
|
||||||
)
|
|
||||||
if new_index.isValid():
|
|
||||||
self.setCurrentIndex(new_index)
|
|
||||||
elif key == Qt.Key_Down: # Arrow key navigation
|
|
||||||
current_index = self.currentIndex()
|
|
||||||
new_index = self.model.index(
|
|
||||||
current_index.row() + 1, current_index.column()
|
|
||||||
)
|
|
||||||
if new_index.isValid():
|
|
||||||
self.setCurrentIndex(new_index)
|
|
||||||
elif key in (Qt.Key_Return, Qt.Key_Enter):
|
|
||||||
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):
|
def toggle_play_pause(self):
|
||||||
"""Toggles the currently playing song by emitting a signal"""
|
"""Toggles the currently playing song by emitting a signal"""
|
||||||
if not self.current_song_filepath:
|
if not self.current_song_filepath:
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
from PyQt5.QtWidgets import QDialog, QVBoxLayout, QLabel, QLineEdit, QPushButton
|
from PyQt5.QtWidgets import QDialog, QFrame, QVBoxLayout, QLabel, QLineEdit, QPushButton
|
||||||
from PyQt5.QtGui import QFont
|
from PyQt5.QtGui import QFont
|
||||||
|
|
||||||
|
|
||||||
@ -6,15 +6,20 @@ class PreferencesWindow(QDialog):
|
|||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
super(PreferencesWindow, self).__init__()
|
super(PreferencesWindow, self).__init__()
|
||||||
self.setWindowTitle("Preferences")
|
self.setWindowTitle("Preferences")
|
||||||
|
self.setMinimumSize(400, 400)
|
||||||
self.config = config
|
self.config = config
|
||||||
layout = QVBoxLayout()
|
layout = QVBoxLayout()
|
||||||
|
|
||||||
label = QLabel("Preferences Window")
|
label = QLabel("Preferences")
|
||||||
|
label.setFont(QFont("Sans", weight=QFont.Bold))
|
||||||
layout.addWidget(label)
|
layout.addWidget(label)
|
||||||
|
|
||||||
# Labels & input fields
|
# Labels & input fields
|
||||||
self.input_fields = {}
|
self.input_fields = {}
|
||||||
for category in self.config.sections():
|
for category in self.config.sections():
|
||||||
|
separator = QFrame()
|
||||||
|
separator.setFrameShape(QFrame.HLine)
|
||||||
|
layout.addWidget(separator)
|
||||||
category_label = QLabel(f"{category}")
|
category_label = QLabel(f"{category}")
|
||||||
category_label.setFont(QFont("Sans", weight=QFont.Bold)) # bold category
|
category_label.setFont(QFont("Sans", weight=QFont.Bold)) # bold category
|
||||||
category_label.setStyleSheet(
|
category_label.setStyleSheet(
|
||||||
|
|||||||
@ -14,7 +14,6 @@ def add_files_to_library(files):
|
|||||||
"""
|
"""
|
||||||
if not files:
|
if not files:
|
||||||
return []
|
return []
|
||||||
# print(f"utils/add_files_to_library: {files}")
|
|
||||||
extensions = config.get("settings", "extensions").split(",")
|
extensions = config.get("settings", "extensions").split(",")
|
||||||
insert_data = [] # To store data for batch insert
|
insert_data = [] # To store data for batch insert
|
||||||
for filepath in files:
|
for filepath in files:
|
||||||
@ -59,7 +58,7 @@ def add_files_to_library(files):
|
|||||||
genre,
|
genre,
|
||||||
filename.split(".")[-1],
|
filename.split(".")[-1],
|
||||||
date,
|
date,
|
||||||
bitrate
|
bitrate,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
# Check if batch size is reached
|
# Check if batch size is reached
|
||||||
|
|||||||
@ -26,12 +26,19 @@ def create_waveform_from_file(file):
|
|||||||
start_index = 0
|
start_index = 0
|
||||||
while start_index < len(audio_data):
|
while start_index < len(audio_data):
|
||||||
end_index = start_index + process_chunk_size
|
end_index = start_index + process_chunk_size
|
||||||
signal = audio_data[start_index:end_index].astype(float) # Get chunk and convert to float
|
signal = audio_data[start_index:end_index].astype(
|
||||||
|
float
|
||||||
|
) # Get chunk and convert to float
|
||||||
|
|
||||||
# Take mean of absolute values per 0.5 seconds
|
# Take mean of absolute values per 0.5 seconds
|
||||||
sub_waveform = np.nanmean(
|
sub_waveform = np.nanmean(
|
||||||
np.pad(np.absolute(signal), (0, ((downsample - (signal.size % downsample)) % downsample)), mode='constant', constant_values=np.NaN).reshape(-1, downsample),
|
np.pad(
|
||||||
axis=1
|
np.absolute(signal),
|
||||||
|
(0, ((downsample - (signal.size % downsample)) % downsample)),
|
||||||
|
mode="constant",
|
||||||
|
constant_values=np.NaN,
|
||||||
|
).reshape(-1, downsample),
|
||||||
|
axis=1,
|
||||||
)
|
)
|
||||||
|
|
||||||
waveform = np.concatenate((waveform, sub_waveform))
|
waveform = np.concatenate((waveform, sub_waveform))
|
||||||
@ -39,17 +46,22 @@ def create_waveform_from_file(file):
|
|||||||
|
|
||||||
# Plot waveforms
|
# Plot waveforms
|
||||||
plt.figure(1)
|
plt.figure(1)
|
||||||
plt.plot(waveform, color='blue')
|
plt.plot(waveform, color="blue")
|
||||||
plt.plot(-waveform, color='blue') # Mirrored waveform
|
plt.plot(-waveform, color="blue") # Mirrored waveform
|
||||||
# Fill in area
|
# Fill in area
|
||||||
plt.fill_between(np.arange(len(waveform)), waveform, -waveform, color='blue', alpha=0.5)
|
plt.fill_between(
|
||||||
|
np.arange(len(waveform)), waveform, -waveform, color="blue", alpha=0.5
|
||||||
|
)
|
||||||
# Remove decorations, labels, axes, etc
|
# Remove decorations, labels, axes, etc
|
||||||
plt.axis('off')
|
plt.axis("off")
|
||||||
# Graph goes to ends of pic
|
# Graph goes to ends of pic
|
||||||
plt.xlim(0, len(waveform))
|
plt.xlim(0, len(waveform))
|
||||||
# Save pic
|
# Save pic
|
||||||
plt.savefig('assets/now_playing_waveform.png', dpi=64, bbox_inches='tight', pad_inches=0)
|
plt.savefig(
|
||||||
|
"assets/now_playing_waveform.png", dpi=64, bbox_inches="tight", pad_inches=0
|
||||||
|
)
|
||||||
# Show me tho
|
# Show me tho
|
||||||
# plt.show()
|
# plt.show()
|
||||||
|
|
||||||
|
|
||||||
# create_waveform_from_file(sys.argv[1])
|
# create_waveform_from_file(sys.argv[1])
|
||||||
@ -19,7 +19,7 @@ class FFTAnalyser(QtCore.QThread):
|
|||||||
|
|
||||||
calculated_visual = QtCore.pyqtSignal(np.ndarray)
|
calculated_visual = QtCore.pyqtSignal(np.ndarray)
|
||||||
|
|
||||||
def __init__(self, player: 'MusicPlayer'): # noqa: F821
|
def __init__(self, player): # noqa: F821
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.player = player
|
self.player = player
|
||||||
self.reset_media()
|
self.reset_media()
|
||||||
@ -32,7 +32,7 @@ class FFTAnalyser(QtCore.QThread):
|
|||||||
def reset_media(self):
|
def reset_media(self):
|
||||||
"""Resets the media to the currently playing song."""
|
"""Resets the media to the currently playing song."""
|
||||||
audio_file = self.player.currentMedia().canonicalUrl().path()
|
audio_file = self.player.currentMedia().canonicalUrl().path()
|
||||||
if os.name == 'nt' and audio_file.startswith('/'):
|
if os.name == "nt" and audio_file.startswith("/"):
|
||||||
audio_file = audio_file[1:]
|
audio_file = audio_file[1:]
|
||||||
if audio_file:
|
if audio_file:
|
||||||
try:
|
try:
|
||||||
@ -52,13 +52,15 @@ class FFTAnalyser(QtCore.QThread):
|
|||||||
"""Calculates the amplitudes used for visualising the media."""
|
"""Calculates the amplitudes used for visualising the media."""
|
||||||
|
|
||||||
sample_count = int(self.song.frame_rate * 0.05)
|
sample_count = int(self.song.frame_rate * 0.05)
|
||||||
start_index = int((self.player.position()/1000) * self.song.frame_rate)
|
start_index = int((self.player.position() / 1000) * self.song.frame_rate)
|
||||||
v_sample = self.samples[start_index:start_index+sample_count] # samples to analyse
|
v_sample = self.samples[
|
||||||
|
start_index : start_index + sample_count
|
||||||
|
] # samples to analyse
|
||||||
|
|
||||||
# use FFTs to analyse frequency and amplitudes
|
# use FFTs to analyse frequency and amplitudes
|
||||||
fourier = np.fft.fft(v_sample)
|
fourier = np.fft.fft(v_sample)
|
||||||
freq = np.fft.fftfreq(fourier.size, d=0.05)
|
freq = np.fft.fftfreq(fourier.size, d=0.05)
|
||||||
amps = 2/v_sample.size * np.abs(fourier)
|
amps = 2 / v_sample.size * np.abs(fourier)
|
||||||
data = np.array([freq, amps]).T
|
data = np.array([freq, amps]).T
|
||||||
|
|
||||||
point_range = 1 / self.resolution
|
point_range = 1 / self.resolution
|
||||||
@ -73,17 +75,26 @@ class FFTAnalyser(QtCore.QThread):
|
|||||||
if not amps.size:
|
if not amps.size:
|
||||||
point_samples.append(0)
|
point_samples.append(0)
|
||||||
else:
|
else:
|
||||||
point_samples.append(amps.max()*((1+self.sensitivity/10+(self.sensitivity-1)/10)**(n/50)))
|
point_samples.append(
|
||||||
|
amps.max()
|
||||||
|
* (
|
||||||
|
(1 + self.sensitivity / 10 + (self.sensitivity - 1) / 10)
|
||||||
|
** (n / 50)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
# Add the point_samples to the self.points array, the reason we have a separate
|
# Add the point_samples to the self.points array, the reason we have a separate
|
||||||
# array (self.bars) is so that we can fade out the previous amplitudes from
|
# array (self.bars) is so that we can fade out the previous amplitudes from
|
||||||
# the past
|
# the past
|
||||||
for n, amp in enumerate(point_samples):
|
for n, amp in enumerate(point_samples):
|
||||||
|
|
||||||
amp *= 2
|
amp *= 2
|
||||||
|
|
||||||
if (self.points[n] > 0 and amp < self.points[n] or
|
if (
|
||||||
self.player.state() in (self.player.PausedState, self.player.StoppedState)):
|
self.points[n] > 0
|
||||||
|
and amp < self.points[n]
|
||||||
|
or self.player.state()
|
||||||
|
in (self.player.PausedState, self.player.StoppedState)
|
||||||
|
):
|
||||||
self.points[n] -= self.points[n] / 10 # fade out
|
self.points[n] -= self.points[n] / 10 # fade out
|
||||||
elif abs(self.points[n] - amp) > self.visual_delta_threshold:
|
elif abs(self.points[n] - amp) > self.visual_delta_threshold:
|
||||||
self.points[n] = amp
|
self.points[n] = amp
|
||||||
|
|||||||
@ -14,7 +14,7 @@ def get_id3_tags(file):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
audio = ID3(file)
|
audio = ID3(file)
|
||||||
except:
|
except Exception:
|
||||||
audio = {}
|
audio = {}
|
||||||
|
|
||||||
# Check if all tags are empty
|
# Check if all tags are empty
|
||||||
@ -24,10 +24,10 @@ def get_id3_tags(file):
|
|||||||
frame = TIT2(encoding=3, text=[title])
|
frame = TIT2(encoding=3, text=[title])
|
||||||
audio["TIT2"] = frame
|
audio["TIT2"] = frame
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f'get_id3_tags.py | Exception: {e}')
|
print(f"get_id3_tags.py | Exception: {e}")
|
||||||
pass
|
pass
|
||||||
try:
|
try:
|
||||||
audio.save() # type: ignore
|
audio.save() # type: ignore
|
||||||
except:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
return audio
|
return audio
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import os
|
import os
|
||||||
import DBA
|
import DBA
|
||||||
from configparser import ConfigParser
|
from configparser import ConfigParser
|
||||||
from utils import get_id3_tags
|
from utils import add_files_to_library, get_id3_tags
|
||||||
from utils import safe_get
|
from utils import safe_get
|
||||||
|
|
||||||
config = ConfigParser()
|
config = ConfigParser()
|
||||||
@ -10,57 +10,7 @@ config.read("config.ini")
|
|||||||
|
|
||||||
def scan_for_music():
|
def scan_for_music():
|
||||||
root_dir = config.get("directories", "library")
|
root_dir = config.get("directories", "library")
|
||||||
extensions = config.get("settings", "extensions").split(",")
|
|
||||||
insert_data = [] # To store data for batch insert
|
|
||||||
|
|
||||||
for dirpath, dirnames, filenames in os.walk(root_dir):
|
# for dirpath, dirnames, filenames ...
|
||||||
for filename in filenames:
|
for _, _, filenames in os.walk(root_dir):
|
||||||
if any(filename.lower().endswith(ext) for ext in extensions):
|
add_files_to_library(filenames)
|
||||||
filepath = os.path.join(dirpath, filename)
|
|
||||||
audio = get_id3_tags(filepath)
|
|
||||||
if "title" not in audio:
|
|
||||||
return
|
|
||||||
|
|
||||||
# Append data tuple to insert_data list
|
|
||||||
insert_data.append(
|
|
||||||
(
|
|
||||||
filepath,
|
|
||||||
safe_get(audio, "title", [])[0],
|
|
||||||
safe_get(audio, "album", [])[0] if "album" in audio else None,
|
|
||||||
safe_get(audio, "artist", [])[0] if "artist" in audio else None,
|
|
||||||
",".join(safe_get(audio, "genre", [])) if "genre" in audio else None,
|
|
||||||
filename.split(".")[-1],
|
|
||||||
safe_get(audio, "date", [])[0] if "date" in audio else None,
|
|
||||||
safe_get(audio, "bitrate", [])[0] if "birate" in audio else None,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
# Check if batch size is reached
|
|
||||||
if len(insert_data) >= 1000:
|
|
||||||
with DBA.DBAccess() as db:
|
|
||||||
db.executemany(
|
|
||||||
"INSERT OR IGNORE INTO library (filepath, title, album, artist, genre, codec, album_date, bitrate) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
|
||||||
insert_data,
|
|
||||||
)
|
|
||||||
insert_data = [] # Reset the insert_data list
|
|
||||||
|
|
||||||
# Insert any remaining data
|
|
||||||
if insert_data:
|
|
||||||
with DBA.DBAccess() as db:
|
|
||||||
db.executemany(
|
|
||||||
"INSERT OR IGNORE INTO library (filepath, title, album, artist, genre, codec, album_date, bitrate) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
|
|
||||||
insert_data,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# id int unsigned auto_increment,
|
|
||||||
# title varchar(255),
|
|
||||||
# album varchar(255),
|
|
||||||
# artist varchar(255),
|
|
||||||
# genre varchar(255),
|
|
||||||
# codec varchar(15),
|
|
||||||
# album_date date,
|
|
||||||
# bitrate int unsigned,
|
|
||||||
# date_added TIMESTAMP default CURRENT_TIMESTAMP,
|
|
||||||
|
|
||||||
# scan_for_music(config.get('directories', 'library1'))
|
|
||||||
|
|||||||
@ -1,11 +1,8 @@
|
|||||||
from components.ErrorDialog import ErrorDialog
|
from components.ErrorDialog import ErrorDialog
|
||||||
from utils.get_id3_tags import get_id3_tags
|
|
||||||
from utils.handle_year_and_date_id3_tag import handle_year_and_date_id3_tag
|
from utils.handle_year_and_date_id3_tag import handle_year_and_date_id3_tag
|
||||||
from mutagen.id3 import ID3
|
from mutagen.id3 import ID3
|
||||||
from mutagen.id3._util import ID3NoHeaderError
|
from mutagen.id3._util import ID3NoHeaderError
|
||||||
from mutagen.mp3 import MP3
|
from mutagen.id3._frames import (
|
||||||
from mutagen.easyid3 import EasyID3
|
|
||||||
from mutagen.id3 import (
|
|
||||||
Frame,
|
Frame,
|
||||||
TIT2,
|
TIT2,
|
||||||
TPE1,
|
TPE1,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user