Music-Library/tab_musiclibrary.py
2024-04-15 13:21:43 +00:00

302 lines
10 KiB
Python

import traceback
from PyQt6.QtWidgets import (
QApplication, QMainWindow, QLabel, QPushButton, QLineEdit,
QWidget, QFrame, QSplitter, QStackedWidget, QListWidget, QListWidgetItem,
QGraphicsScene, QGraphicsView, QGraphicsPixmapItem,
QLayout, QScrollArea, QVBoxLayout, QHBoxLayout,
QDialog, QFileDialog
)
from PyQt6.QtGui import QPixmap
from PyQt6.QtCore import Qt, QAbstractListModel
from dialogs import ErrorPopup, ListDialog
import os, sys, json, time
from extractor import ExtractMetadata, ExtractMediaData
from action_runner import RunningDialog
def calculateAspectRatio(pixmap, width, height, aspectRatioMode):
return pixmap.scaled(width, height, aspectRatioMode)
# E:\Downloads\4kvideodownloader
class FolderScanner:
def __init__(self, path):
self.path = path
def scan(self):
data = []
if not os.path.exists(self.path):
print("Path does not exist: ", self.path)
return data
for root, dirs, files in os.walk(self.path):
for file in files:
if file.endswith(".mp3"):
p = os.path.join(root, file)
metadata = ExtractMetadata(p)
# debug.json
# with open(f"{file}.debug.json", "w") as f:
# json.dump(metadata, f, indent=4)
data.append(metadata)
metadata.update({
"file:": p
})
if "data" not in data:
metadata.update({
"thumbnail": self.ExtractImageData(p, f"thumbnails\\{file}.png"),
})
return data
def ExtractImageData(self, file, output):
try:
return ExtractMediaData(file, output)
except Exception as e:
print("### ExtractImageData Error ###")
traceback.print_exc()
pass
class MusicModel(QAbstractListModel):
def __init__(self, data):
super().__init__()
self._data = data
def rowCount(self, parent):
return len(self._data)
def data(self, index, role):
if role == Qt.ItemDataRole.DisplayRole:
return self._data[index.row()]
class MusicModelWidget(QFrame):
def __init__(self, parent = None, name = None, path = None):
super(MusicModelWidget, self).__init__(parent)
self.setObjectName(name)
layout = QHBoxLayout()
self.image = image( "default.png" )
self.image.setFixedSize(64, 64)
iteminfo_base = QWidget()
iteminfo = QVBoxLayout(iteminfo_base)
iteminfo.setSpacing(0)
self.itemname = QLabel(name)
self.itemname.setObjectName("itemname")
self.artist = QLabel("Artist")
iteminfo.addWidget(self.itemname)
iteminfo.addWidget(self.artist)
layout.addWidget(self.image)
layout.addWidget(iteminfo_base)
self.setStyleSheet("#itemname { font-size: 16px; }")
self.setLayout(layout)
def openFile(self):
os.startfile(self.data["format"]["filename"])
class image(QFrame):
def __init__(self, path = None):
super().__init__()
if path is None:
path = "default.png"
self.setObjectName("Logo")
self.setFrameShadow(QFrame.Shadow.Raised)
self.setFrameShape(QFrame.Shape.StyledPanel)
self.sceene = QGraphicsScene(self)
self.view = QGraphicsView(self.sceene, self)
self.view.setFixedSize(self.width(), self.height())
self.view.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
self.view.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
self.view.move(0, 0)
# remove border
self.view.setLineWidth(0)
self.view.setMidLineWidth(0)
self.view.setFrameShadow(QFrame.Shadow.Plain)
self.view.setFrameShape(QFrame.Shape.NoFrame)
self.view.setStyleSheet("background-color: transparent;")
self.pixmap = None
self.pixmap = QGraphicsPixmapItem()
self.sceene.addItem(self.pixmap)
try:
self.pixmap.setPixmap(QPixmap(path).scaled(64, 64, Qt.AspectRatioMode.KeepAspectRatio))
except Exception as e:
print("### Init Error ###")
traceback.print_exc()
def loadimage(self, path):
try:
print("Setting image: ", path)
self.pixmap.setPixmap(QPixmap(path).scaled(64, 64, Qt.AspectRatioMode.KeepAspectRatio))
except Exception as e:
print("### Load Image Error ###")
traceback.print_exc()
def resizeEvent(self, event):
self.view.setFixedSize(self.width(), self.height())
calculateAspectRatio(self.pixmap.pixmap(), self.width(), self.height(), Qt.AspectRatioMode.KeepAspectRatio)
class MusicItem:
def __init__(self):
self.data = None
self.image = None
self.title = None
self.artist = None
self.imagepath = None
def setupData(self, ffprobeJsonData):
self.data = ffprobeJsonData
data_format = self.data["format"]
# get title from the tags
if "tags" in data_format:
if "title" in data_format["tags"]:
self.title = data_format["tags"]["title"]
if "artist" in data_format["tags"]:
self.artist = data_format["tags"]["artist"]
if "thumbnail" in self.data:
self.image = self.data["thumbnail"]
def getImage(self):
return self.image
def openFile(self):
try:
os.startfile(self.data['file:'])
except Exception as e:
print("Unable to open file: ", e)
traceback.print_exc()
class MusicLibraryTab(QFrame):
def __init__(self):
super().__init__()
self.list = QListWidget()
self.scanpathInput = QLineEdit()
self.scanpathInput.setPlaceholderText("Path to scan")
self.scanbutton = QPushButton("Scan")
self.scanbutton.clicked.connect(self.scan)
self.list.setObjectName("list")
self.list.setLineWidth(2)
self.list.setMidLineWidth(2)
self.list.setFrameShadow(QFrame.Shadow.Raised)
self.list.setFrameShape(QFrame.Shape.StyledPanel)
self.list.doubleClicked.connect(lambda: self.list.currentItem().data(0).openFile())
scan_base = QWidget()
scan_layout = QHBoxLayout(scan_base)
scan_layout.setSpacing(0)
scan_layout.setContentsMargins(0, 0, 0, 0)
scan_layout.addWidget(self.scanbutton)
scan_layout.addWidget(self.scanpathInput)
layout = QVBoxLayout()
layout.setSpacing(0)
layout.addWidget(scan_base)
layout.addWidget(self.list)
self.setLayout(layout)
# check if scan.json exists
if os.path.exists("scan.json"):
self.scan("scan.json")
def scan(self, file = None):
path = self.scanpathInput.text()
musicscanner = FolderScanner(path)
runningDialog = RunningDialog(None)
try:
# clear list
self.list.clear()
runningDialog.show()
runningDialog.setWindowTitle("Scanning: " + path)
scandate = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
if not file:
# formated date time
scandate = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
scan = musicscanner.scan()
else:
loadeddata = json.load(open(file))["scan"]
scan = loadeddata
unknownData = False
unknownFiles = []
# debug.scan.json
with open("scan.json", "w") as f:
settings={
"path": self.scanpathInput.text(),
"scanned": scandate
}
finaldata ={
"settings": settings,
"scan": scan
}
json.dump(finaldata, f, indent=4)
# check if any of the data is unknown
for data in scan:
# does data key exist
if "data" in data:
# is data key unknown
if data["data"] == "Unknown":
print("Unknown data: ", data["file:"])
unknownData = True
unknownFiles.append(data["file:"])
#
music = MusicItem()
try:
music.setupData(data)
except Exception as e:
print("Skipping setup data: ", e)
item = QListWidgetItem(self.list)
item.setData(0, music)
item_frame = MusicModelWidget(self.list, music.title)
if music.image:
runningDialog.setWindowTitle("Loading Image: " + music.image)
item_frame.image.loadimage(music.image)
item.setSizeHint(item_frame.sizeHint())
if music.artist:
item_frame.artist.setText(music.artist)
else:
item_frame.artist.setText("Unknown Artist")
# double click
# item_frame.itemname.mouseDoubleClickEvent = os.startfile(data["format"]["filename"])
self.list.addItem(item)
self.list.setItemWidget(item, item_frame)
if unknownData:
ListDialog(None, "Unknown Files", "Some files metadata could not be extracted", unknownFiles)
runningDialog.close()
except Exception as e:
runningDialog.close()
traceback.print_exc()
try:
ErrorPopup(self, str(e), str(traceback.format_exc(
limit=1, chain=True
))).exec()
except Exception as e:
print("Unable to show error popup: ", e)
pass
def resizeEvent(self, event):
self.list.resize(self.width(), self.height())