add user custom playlist support
This commit is contained in:
+14
-4
@@ -4,7 +4,7 @@ project(SubWave LANGUAGES CXX)
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS Widgets Multimedia OpenGL OpenGLWidgets)
|
||||
find_package(Qt6 REQUIRED COMPONENTS Widgets Multimedia OpenGL OpenGLWidgets Core)
|
||||
|
||||
# require ffmpeg
|
||||
find_package(PkgConfig REQUIRED)
|
||||
@@ -35,7 +35,10 @@ set(SOURCES
|
||||
src/GradientDialog.cpp
|
||||
src/EqualizerWidget.cpp
|
||||
src/TrackDelegate.cpp
|
||||
src/PlaylistModel.cpp
|
||||
src/playlists/PlaylistModel.cpp
|
||||
src/playlists/PlaylistManager.cpp
|
||||
src/playlists/PlaylistCreatorDialog.cpp
|
||||
src/playlists/PlaylistBrowserDialog.cpp
|
||||
src/MainWindow.cpp
|
||||
)
|
||||
|
||||
@@ -50,7 +53,10 @@ set(HEADERS
|
||||
src/GradientDialog.h
|
||||
src/EqualizerWidget.h
|
||||
src/TrackDelegate.h
|
||||
src/PlaylistModel.h
|
||||
src/playlists/PlaylistModel.h
|
||||
src/playlists/PlaylistManager.h
|
||||
src/playlists/PlaylistCreatorDialog.h
|
||||
src/playlists/PlaylistBrowserDialog.h
|
||||
src/MainWindow.h
|
||||
)
|
||||
|
||||
@@ -66,7 +72,11 @@ if(WIN32)
|
||||
set_target_properties(SubWave PROPERTIES WIN32_EXECUTABLE TRUE)
|
||||
endif()
|
||||
|
||||
target_include_directories(SubWave PRIVATE src src/engine)
|
||||
target_include_directories(SubWave PRIVATE
|
||||
src
|
||||
src/engine
|
||||
src/playlists
|
||||
)
|
||||
|
||||
target_link_libraries(SubWave PRIVATE
|
||||
Qt6::Widgets
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
#include "SpectrogramWidget.h"
|
||||
#include "EqualizerWidget.h"
|
||||
#include "Config.h"
|
||||
#include "PlaylistCreatorDialog.h"
|
||||
#include "PlaylistBrowserDialog.h"
|
||||
#include "PlaylistManager.h"
|
||||
|
||||
#include <QVBoxLayout>
|
||||
#include <QHBoxLayout>
|
||||
@@ -187,6 +190,15 @@ void MainWindow::buildMenuBar()
|
||||
quitAct->setShortcut(QKeySequence::Quit);
|
||||
connect(quitAct, &QAction::triggered, qApp, &QApplication::quit);
|
||||
|
||||
// Playlists menu
|
||||
auto *playlists = mb->addMenu("&Playlists");
|
||||
|
||||
auto *newPlaylistAct = playlists->addAction("Create Playlist…");
|
||||
connect(newPlaylistAct, &QAction::triggered, this, &MainWindow::onCreatePlaylist);
|
||||
|
||||
auto *loadPlaylistAct = playlists->addAction("Load Playlist…");
|
||||
connect(loadPlaylistAct, &QAction::triggered, this, &MainWindow::onLoadPlaylist);
|
||||
|
||||
auto *help = mb->addMenu("&Help");
|
||||
auto *about = help->addAction("About SubWave");
|
||||
connect(about, &QAction::triggered, this, [this]{
|
||||
@@ -358,4 +370,51 @@ void MainWindow::onUiTimer()
|
||||
|
||||
if (m_engine->state() == AudioEngine::State::Stopped)
|
||||
m_btnPlay->setText("Play");
|
||||
}
|
||||
|
||||
// ── Playlist Management ─────────────────────────────────────────────────────
|
||||
|
||||
void MainWindow::onCreatePlaylist()
|
||||
{
|
||||
PlaylistCreatorDialog dialog(this);
|
||||
if (dialog.exec() == QDialog::Accepted) {
|
||||
// Playlist was created and saved
|
||||
// Optionally auto-load it
|
||||
auto playlist = dialog.getPlaylist();
|
||||
loadPlaylistIntoModel(playlist.name);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::onLoadPlaylist()
|
||||
{
|
||||
PlaylistBrowserDialog dialog(this);
|
||||
if (dialog.exec() == QDialog::Accepted) {
|
||||
QString playlistName = dialog.getSelectedPlaylist();
|
||||
if (!playlistName.isEmpty()) {
|
||||
loadPlaylistIntoModel(playlistName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::loadPlaylistIntoModel(const QString &playlistName)
|
||||
{
|
||||
auto playlist = PlaylistManager::loadPlaylist(playlistName);
|
||||
|
||||
if (playlist.tracks.isEmpty()) {
|
||||
QMessageBox::warning(this, "Empty Playlist",
|
||||
QString("Playlist \"%1\" is empty.").arg(playlistName));
|
||||
return;
|
||||
}
|
||||
|
||||
m_model->setTracks(playlist.tracks);
|
||||
m_lblStatus->setText(
|
||||
QString("Playlist: %1 (%2 tracks)")
|
||||
.arg(playlistName)
|
||||
.arg(playlist.tracks.size())
|
||||
);
|
||||
|
||||
// Reset playback state
|
||||
m_currentIndex = -1;
|
||||
m_engine->stop();
|
||||
m_btnPlay->setText("Play");
|
||||
}
|
||||
@@ -17,6 +17,8 @@ class PlaylistModel;
|
||||
class CoverArtWidget;
|
||||
class SpectrogramWidget;
|
||||
class EqualizerWidget;
|
||||
class PlaylistCreatorDialog;
|
||||
class PlaylistBrowserDialog;
|
||||
|
||||
class MainWindow : public QMainWindow
|
||||
{
|
||||
@@ -43,6 +45,9 @@ private:
|
||||
void playNext();
|
||||
void playPrev();
|
||||
void onUiTimer();
|
||||
void onCreatePlaylist();
|
||||
void onLoadPlaylist();
|
||||
void loadPlaylistIntoModel(const QString &playlistName);
|
||||
|
||||
// Core
|
||||
AudioEngine *m_engine;
|
||||
|
||||
@@ -80,4 +80,4 @@ void EqProcessor::recomputeBiquad(int band) {
|
||||
m_coeff[band][4] = float(a2 / a0);
|
||||
}
|
||||
|
||||
} // namespace engine
|
||||
} // namespace engine
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
#include "PlaylistBrowserDialog.h"
|
||||
#include <QVBoxLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QMessageBox>
|
||||
|
||||
PlaylistBrowserDialog::PlaylistBrowserDialog(QWidget *parent)
|
||||
: QDialog(parent)
|
||||
{
|
||||
setWindowTitle("Load Playlist");
|
||||
setMinimumWidth(400);
|
||||
setMinimumHeight(350);
|
||||
buildUI();
|
||||
refreshPlaylistList();
|
||||
}
|
||||
|
||||
void PlaylistBrowserDialog::buildUI()
|
||||
{
|
||||
auto *vlay = new QVBoxLayout(this);
|
||||
|
||||
vlay->addWidget(new QLabel("Available Playlists:"));
|
||||
|
||||
m_playlistList = new QListWidget;
|
||||
m_playlistList->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
vlay->addWidget(m_playlistList);
|
||||
|
||||
connect(m_playlistList, &QListWidget::itemDoubleClicked,
|
||||
this, &PlaylistBrowserDialog::onPlaylistDoubleClicked);
|
||||
|
||||
// Buttons
|
||||
auto *btnRow = new QHBoxLayout;
|
||||
btnRow->addStretch();
|
||||
|
||||
m_deleteBtn = new QPushButton("Delete");
|
||||
m_loadBtn = new QPushButton("Load");
|
||||
|
||||
btnRow->addWidget(m_deleteBtn);
|
||||
btnRow->addWidget(m_loadBtn);
|
||||
|
||||
auto *closeBtn = new QPushButton("Close");
|
||||
btnRow->addWidget(closeBtn);
|
||||
|
||||
vlay->addLayout(btnRow);
|
||||
|
||||
connect(m_deleteBtn, &QPushButton::clicked, this, &PlaylistBrowserDialog::onDeletePlaylist);
|
||||
connect(m_loadBtn, &QPushButton::clicked, this, &PlaylistBrowserDialog::onLoadPlaylist);
|
||||
connect(closeBtn, &QPushButton::clicked, this, &QDialog::reject);
|
||||
}
|
||||
|
||||
void PlaylistBrowserDialog::onPlaylistDoubleClicked(QListWidgetItem *item)
|
||||
{
|
||||
if (item) {
|
||||
m_selectedPlaylist = item->text();
|
||||
accept();
|
||||
}
|
||||
}
|
||||
|
||||
void PlaylistBrowserDialog::onLoadPlaylist()
|
||||
{
|
||||
QListWidgetItem *current = m_playlistList->currentItem();
|
||||
if (!current) {
|
||||
QMessageBox::warning(this, "No Selection", "Please select a playlist to load.");
|
||||
return;
|
||||
}
|
||||
|
||||
m_selectedPlaylist = current->text();
|
||||
accept();
|
||||
}
|
||||
|
||||
void PlaylistBrowserDialog::onDeletePlaylist()
|
||||
{
|
||||
QListWidgetItem *current = m_playlistList->currentItem();
|
||||
if (!current) {
|
||||
QMessageBox::warning(this, "No Selection", "Please select a playlist to delete.");
|
||||
return;
|
||||
}
|
||||
|
||||
QString playlistName = current->text();
|
||||
|
||||
int ret = QMessageBox::warning(this, "Confirm Delete",
|
||||
QString("Are you sure you want to delete the playlist \"%1\"?").arg(playlistName),
|
||||
QMessageBox::Yes | QMessageBox::No);
|
||||
|
||||
if (ret == QMessageBox::Yes) {
|
||||
if (PlaylistManager::deletePlaylist(playlistName)) {
|
||||
QMessageBox::information(this, "Success",
|
||||
QString("Playlist \"%1\" deleted.").arg(playlistName));
|
||||
refreshPlaylistList();
|
||||
} else {
|
||||
QMessageBox::critical(this, "Error", "Failed to delete playlist.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PlaylistBrowserDialog::refreshPlaylistList()
|
||||
{
|
||||
m_playlistList->clear();
|
||||
|
||||
QStringList playlists = PlaylistManager::listPlaylists();
|
||||
for (const QString &name : playlists) {
|
||||
m_playlistList->addItem(name);
|
||||
}
|
||||
|
||||
if (playlists.isEmpty()) {
|
||||
m_playlistList->addItem("(No playlists created)");
|
||||
m_deleteBtn->setEnabled(false);
|
||||
m_loadBtn->setEnabled(false);
|
||||
} else {
|
||||
m_deleteBtn->setEnabled(true);
|
||||
m_loadBtn->setEnabled(true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include <QDialog>
|
||||
#include <QPushButton>
|
||||
#include <QListWidget>
|
||||
#include "PlaylistManager.h"
|
||||
|
||||
class PlaylistBrowserDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit PlaylistBrowserDialog(QWidget *parent = nullptr);
|
||||
QString getSelectedPlaylist() const { return m_selectedPlaylist; }
|
||||
|
||||
private slots:
|
||||
void onPlaylistDoubleClicked(QListWidgetItem *item);
|
||||
void onDeletePlaylist();
|
||||
void onLoadPlaylist();
|
||||
void refreshPlaylistList();
|
||||
|
||||
private:
|
||||
void buildUI();
|
||||
|
||||
QListWidget *m_playlistList;
|
||||
QPushButton *m_deleteBtn;
|
||||
QPushButton *m_loadBtn;
|
||||
QString m_selectedPlaylist;
|
||||
};
|
||||
@@ -0,0 +1,155 @@
|
||||
#include "PlaylistCreatorDialog.h"
|
||||
#include "MetadataReader.h"
|
||||
#include <QVBoxLayout>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QFileDialog>
|
||||
#include <QMessageBox>
|
||||
#include <QFileInfo>
|
||||
|
||||
PlaylistCreatorDialog::PlaylistCreatorDialog(QWidget *parent)
|
||||
: QDialog(parent)
|
||||
{
|
||||
setWindowTitle("Create Playlist");
|
||||
setMinimumWidth(500);
|
||||
setMinimumHeight(400);
|
||||
buildUI();
|
||||
}
|
||||
|
||||
void PlaylistCreatorDialog::buildUI()
|
||||
{
|
||||
auto *vlay = new QVBoxLayout(this);
|
||||
|
||||
// Name input
|
||||
auto *nameRow = new QHBoxLayout;
|
||||
nameRow->addWidget(new QLabel("Playlist Name:"));
|
||||
m_nameEdit = new QLineEdit;
|
||||
m_nameEdit->setPlaceholderText("e.g., My Favorite Songs");
|
||||
nameRow->addWidget(m_nameEdit);
|
||||
vlay->addLayout(nameRow);
|
||||
|
||||
// Tracks list
|
||||
vlay->addWidget(new QLabel("Tracks:"));
|
||||
m_trackList = new QListWidget;
|
||||
m_trackList->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
vlay->addWidget(m_trackList);
|
||||
|
||||
// Buttons for track management
|
||||
auto *trackBtnRow = new QHBoxLayout;
|
||||
m_addBtn = new QPushButton("Add Files...");
|
||||
m_removeBtn = new QPushButton("Remove Selected");
|
||||
trackBtnRow->addWidget(m_addBtn);
|
||||
trackBtnRow->addWidget(m_removeBtn);
|
||||
trackBtnRow->addStretch();
|
||||
vlay->addLayout(trackBtnRow);
|
||||
|
||||
connect(m_addBtn, &QPushButton::clicked, this, &PlaylistCreatorDialog::onAddFiles);
|
||||
connect(m_removeBtn, &QPushButton::clicked, this, &PlaylistCreatorDialog::onRemoveSelected);
|
||||
|
||||
// Dialog buttons
|
||||
vlay->addSpacing(10);
|
||||
auto *dialogBtnRow = new QHBoxLayout;
|
||||
dialogBtnRow->addStretch();
|
||||
|
||||
m_createBtn = new QPushButton("Create Playlist");
|
||||
auto *cancelBtn = new QPushButton("Cancel");
|
||||
|
||||
dialogBtnRow->addWidget(m_createBtn);
|
||||
dialogBtnRow->addWidget(cancelBtn);
|
||||
|
||||
vlay->addLayout(dialogBtnRow);
|
||||
|
||||
connect(m_createBtn, &QPushButton::clicked, this, &PlaylistCreatorDialog::onCreatePlaylist);
|
||||
connect(cancelBtn, &QPushButton::clicked, this, &QDialog::reject);
|
||||
}
|
||||
|
||||
void PlaylistCreatorDialog::onAddFiles()
|
||||
{
|
||||
QStringList files = QFileDialog::getOpenFileNames(
|
||||
this,
|
||||
"Select audio files",
|
||||
QDir::homePath(),
|
||||
"Audio Files (*.mp3 *.flac *.wav *.ogg *.aac *.m4a *.opus *.wma *.ape *.aiff *.alac);;All Files (*)"
|
||||
);
|
||||
|
||||
for (const QString &filePath : files) {
|
||||
if (filePath.isEmpty()) continue;
|
||||
|
||||
// Check if already in playlist
|
||||
bool alreadyAdded = false;
|
||||
for (const Track &t : m_playlist.tracks) {
|
||||
if (t.filePath == filePath) {
|
||||
alreadyAdded = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!alreadyAdded) {
|
||||
Track track;
|
||||
track.filePath = filePath;
|
||||
|
||||
// Set default metadata
|
||||
QString ext = QFileInfo(filePath).suffix().toLower();
|
||||
track.format = ext.toUpper();
|
||||
track.title = QFileInfo(filePath).completeBaseName();
|
||||
track.artist = "Unknown Artist";
|
||||
track.album = "Unknown Album";
|
||||
|
||||
// Extract actual metadata and cover art
|
||||
MetadataReader::populate(track);
|
||||
|
||||
m_playlist.tracks.append(track);
|
||||
}
|
||||
}
|
||||
|
||||
updateTrackList();
|
||||
}
|
||||
|
||||
void PlaylistCreatorDialog::onRemoveSelected()
|
||||
{
|
||||
int row = m_trackList->currentRow();
|
||||
if (row >= 0 && row < m_playlist.tracks.size()) {
|
||||
m_playlist.tracks.removeAt(row);
|
||||
updateTrackList();
|
||||
}
|
||||
}
|
||||
|
||||
void PlaylistCreatorDialog::onCreatePlaylist()
|
||||
{
|
||||
QString name = m_nameEdit->text().trimmed();
|
||||
|
||||
if (name.isEmpty()) {
|
||||
QMessageBox::warning(this, "Invalid Name", "Please enter a playlist name.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_playlist.tracks.isEmpty()) {
|
||||
QMessageBox::warning(this, "No Tracks", "Please add at least one track to the playlist.");
|
||||
return;
|
||||
}
|
||||
|
||||
m_playlist.name = name;
|
||||
|
||||
if (PlaylistManager::savePlaylist(m_playlist)) {
|
||||
QMessageBox::information(this, "Success",
|
||||
QString("Playlist \"%1\" created with %2 tracks.")
|
||||
.arg(name)
|
||||
.arg(m_playlist.tracks.size()));
|
||||
accept();
|
||||
} else {
|
||||
QMessageBox::critical(this, "Error", "Failed to save playlist.");
|
||||
}
|
||||
}
|
||||
|
||||
void PlaylistCreatorDialog::updateTrackList()
|
||||
{
|
||||
m_trackList->clear();
|
||||
|
||||
for (const Track &track : m_playlist.tracks) {
|
||||
QString display = track.displayTitle();
|
||||
if (!track.artist.isEmpty()) {
|
||||
display = track.artist + " - " + display;
|
||||
}
|
||||
m_trackList->addItem(display);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include <QDialog>
|
||||
#include <QPushButton>
|
||||
#include <QLineEdit>
|
||||
#include <QListWidget>
|
||||
#include "PlaylistManager.h"
|
||||
|
||||
class PlaylistCreatorDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit PlaylistCreatorDialog(QWidget *parent = nullptr);
|
||||
PlaylistManager::Playlist getPlaylist() const { return m_playlist; }
|
||||
|
||||
private slots:
|
||||
void onAddFiles();
|
||||
void onRemoveSelected();
|
||||
void onCreatePlaylist();
|
||||
|
||||
private:
|
||||
void buildUI();
|
||||
void updateTrackList();
|
||||
|
||||
PlaylistManager::Playlist m_playlist;
|
||||
QLineEdit *m_nameEdit;
|
||||
QListWidget *m_trackList;
|
||||
QPushButton *m_addBtn;
|
||||
QPushButton *m_removeBtn;
|
||||
QPushButton *m_createBtn;
|
||||
};
|
||||
@@ -0,0 +1,192 @@
|
||||
#include "PlaylistManager.h"
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QStandardPaths>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QFileInfo>
|
||||
#include "MetadataReader.h"
|
||||
|
||||
QString PlaylistManager::playlistsDir()
|
||||
{
|
||||
return QDir::homePath() + "/.subwave/playlists";
|
||||
}
|
||||
|
||||
bool PlaylistManager::ensurePlaylistsDir()
|
||||
{
|
||||
QDir dir;
|
||||
return dir.mkpath(playlistsDir());
|
||||
}
|
||||
|
||||
QString PlaylistManager::playlistFilePath(const QString &playlistName)
|
||||
{
|
||||
QString sanitized = sanitizePlaylistName(playlistName);
|
||||
return playlistsDir() + "/" + sanitized + ".json";
|
||||
}
|
||||
|
||||
QString PlaylistManager::sanitizePlaylistName(const QString &name)
|
||||
{
|
||||
QString result = name;
|
||||
result.replace('/', "_")
|
||||
.replace('\\', "_")
|
||||
.replace(':', "_")
|
||||
.replace('"', "_")
|
||||
.replace('?', "_")
|
||||
.replace('*', "_")
|
||||
.replace('<', "_")
|
||||
.replace('>', "_")
|
||||
.replace('|', "_");
|
||||
return result.trimmed();
|
||||
}
|
||||
|
||||
PlaylistManager::Playlist PlaylistManager::loadPlaylist(const QString &playlistName)
|
||||
{
|
||||
Playlist playlist;
|
||||
playlist.name = playlistName;
|
||||
playlist.filePath = playlistFilePath(playlistName);
|
||||
|
||||
QFile file(playlist.filePath);
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
return playlist;
|
||||
}
|
||||
|
||||
QString json = QString::fromUtf8(file.readAll());
|
||||
file.close();
|
||||
|
||||
playlist = parsePlaylistJson(json);
|
||||
playlist.name = playlistName;
|
||||
playlist.filePath = playlistFilePath(playlistName);
|
||||
|
||||
return playlist;
|
||||
}
|
||||
|
||||
bool PlaylistManager::savePlaylist(const Playlist &playlist)
|
||||
{
|
||||
if (!ensurePlaylistsDir()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QString filePath = playlistFilePath(playlist.name);
|
||||
QFile file(filePath);
|
||||
|
||||
if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QString json = playlistToJson(playlist);
|
||||
file.write(json.toUtf8());
|
||||
file.close();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PlaylistManager::deletePlaylist(const QString &playlistName)
|
||||
{
|
||||
QString filePath = playlistFilePath(playlistName);
|
||||
QFile file(filePath);
|
||||
return file.remove();
|
||||
}
|
||||
|
||||
QStringList PlaylistManager::listPlaylists()
|
||||
{
|
||||
QStringList result;
|
||||
QDir dir(playlistsDir());
|
||||
|
||||
if (!dir.exists()) {
|
||||
return result;
|
||||
}
|
||||
|
||||
QFileInfoList files = dir.entryInfoList(
|
||||
QStringList() << "*.json",
|
||||
QDir::Files,
|
||||
QDir::Name
|
||||
);
|
||||
|
||||
for (const QFileInfo &info : files) {
|
||||
QString name = info.baseName();
|
||||
result.append(name);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void PlaylistManager::addTrackToPlaylist(Playlist &playlist, const Track &track)
|
||||
{
|
||||
playlist.tracks.append(track);
|
||||
}
|
||||
|
||||
void PlaylistManager::removeTrackFromPlaylist(Playlist &playlist, int index)
|
||||
{
|
||||
if (index >= 0 && index < playlist.tracks.size()) {
|
||||
playlist.tracks.removeAt(index);
|
||||
}
|
||||
}
|
||||
|
||||
void PlaylistManager::clearPlaylist(Playlist &playlist)
|
||||
{
|
||||
playlist.tracks.clear();
|
||||
}
|
||||
|
||||
PlaylistManager::Playlist PlaylistManager::parsePlaylistJson(const QString &json)
|
||||
{
|
||||
Playlist playlist;
|
||||
|
||||
QJsonDocument doc = QJsonDocument::fromJson(json.toUtf8());
|
||||
if (!doc.isObject()) {
|
||||
return playlist;
|
||||
}
|
||||
|
||||
QJsonObject obj = doc.object();
|
||||
if (obj.contains("tracks") && obj["tracks"].isArray()) {
|
||||
QJsonArray tracksArray = obj["tracks"].toArray();
|
||||
|
||||
for (int i = 0; i < tracksArray.size(); ++i) {
|
||||
QJsonObject trackObj = tracksArray[i].toObject();
|
||||
|
||||
Track track;
|
||||
track.filePath = trackObj["filePath"].toString();
|
||||
track.title = trackObj["title"].toString();
|
||||
track.artist = trackObj["artist"].toString();
|
||||
track.album = trackObj["album"].toString();
|
||||
track.year = trackObj["year"].toInt(0);
|
||||
track.trackNumber = trackObj["trackNumber"].toInt(0);
|
||||
track.durationSecs = trackObj["durationSecs"].toInt(0);
|
||||
track.format = trackObj["format"].toString();
|
||||
|
||||
if (!track.filePath.isEmpty()) {
|
||||
// Re-extract metadata and cover art from the file
|
||||
// in case it has changed or cover art wasn't saved
|
||||
MetadataReader::populate(track);
|
||||
playlist.tracks.append(track);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return playlist;
|
||||
}
|
||||
|
||||
QString PlaylistManager::playlistToJson(const Playlist &playlist)
|
||||
{
|
||||
QJsonObject root;
|
||||
QJsonArray tracksArray;
|
||||
|
||||
for (const Track &track : playlist.tracks) {
|
||||
QJsonObject trackObj;
|
||||
trackObj["filePath"] = track.filePath;
|
||||
trackObj["title"] = track.title;
|
||||
trackObj["artist"] = track.artist;
|
||||
trackObj["album"] = track.album;
|
||||
trackObj["year"] = track.year;
|
||||
trackObj["trackNumber"] = track.trackNumber;
|
||||
trackObj["durationSecs"] = static_cast<int>(track.durationSecs);
|
||||
trackObj["format"] = track.format;
|
||||
|
||||
tracksArray.append(trackObj);
|
||||
}
|
||||
|
||||
root["tracks"] = tracksArray;
|
||||
|
||||
QJsonDocument doc(root);
|
||||
return QString::fromUtf8(doc.toJson());
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include <QList>
|
||||
#include "Track.h"
|
||||
|
||||
class PlaylistManager {
|
||||
public:
|
||||
struct Playlist {
|
||||
QString name;
|
||||
QList<Track> tracks;
|
||||
QString filePath;
|
||||
};
|
||||
|
||||
static QString playlistsDir();
|
||||
static bool ensurePlaylistsDir();
|
||||
|
||||
// Playlist management
|
||||
static Playlist loadPlaylist(const QString &playlistName);
|
||||
static bool savePlaylist(const Playlist &playlist);
|
||||
static bool deletePlaylist(const QString &playlistName);
|
||||
static QStringList listPlaylists();
|
||||
|
||||
// Track management
|
||||
static void addTrackToPlaylist(Playlist &playlist, const Track &track);
|
||||
static void removeTrackFromPlaylist(Playlist &playlist, int index);
|
||||
static void clearPlaylist(Playlist &playlist);
|
||||
|
||||
private:
|
||||
static QString playlistFilePath(const QString &playlistName);
|
||||
static QString sanitizePlaylistName(const QString &name);
|
||||
static Playlist parsePlaylistJson(const QString &json);
|
||||
static QString playlistToJson(const Playlist &playlist);
|
||||
};
|
||||
Reference in New Issue
Block a user