Compare commits

..

12 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
ef0065a49c MainWindow: Save tab mode immediately when changed
The sidebar mode (tabs on top, large sidebar, etc.) was only being saved
when the application exits normally. This caused the setting to be lost
if the application crashed or was force-closed, reverting to the default
"Large Sidebar" mode on next startup.

This fix connects the FancyTabWidget::ModeChanged signal to immediately
save the tab_mode setting when the user changes it via the context menu.

Fixes issue where sidebar keeps periodically reverting to Large mode.

Co-authored-by: jonaski <10343810+jonaski@users.noreply.github.com>
2026-01-03 15:34:55 +00:00
copilot-swe-agent[bot]
28fb5a863c Initial plan 2026-01-03 15:12:21 +00:00
Jonas Kvinge
e8d9e1172f FileViewTreeModel: Add const 2026-01-03 16:09:56 +01:00
Alexopus
aac8d4e68b Add file tree view 2026-01-03 15:11:56 +01:00
dependabot[bot]
0e28e800b3 Bump vmactions/freebsd-vm from 1.3.4 to 1.3.5
Bumps [vmactions/freebsd-vm](https://github.com/vmactions/freebsd-vm) from 1.3.4 to 1.3.5.
- [Release notes](https://github.com/vmactions/freebsd-vm/releases)
- [Commits](https://github.com/vmactions/freebsd-vm/compare/v1.3.4...v1.3.5)

---
updated-dependencies:
- dependency-name: vmactions/freebsd-vm
  dependency-version: 1.3.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-02 17:33:02 +01:00
Jonas Kvinge
cf84bc29ab CI: Manually codesign 2026-01-01 01:51:10 +01:00
Jonas Kvinge
afc3effc9d CI: Switch macOS dependencies repo 2025-12-30 20:01:34 +01:00
Jonas Kvinge
370bebff5f CollectionView: Fix Enter/Return behavior to respect double-click settings
Fixes #1691
2025-12-30 19:08:52 +01:00
Jonas Kvinge
db410cc257 MainWindow: Remove unused declaration 2025-12-29 22:14:08 +01:00
Jonas Kvinge
20a9946e51 Song: Prefer filenames with "front" or "cover" for art automatic
Fixes #1745
2025-12-29 21:16:06 +01:00
dependabot[bot]
b6c8ff19af Bump vmactions/freebsd-vm from 1.3.2 to 1.3.4
Bumps [vmactions/freebsd-vm](https://github.com/vmactions/freebsd-vm) from 1.3.2 to 1.3.4.
- [Release notes](https://github.com/vmactions/freebsd-vm/releases)
- [Commits](https://github.com/vmactions/freebsd-vm/compare/v1.3.2...v1.3.4)

---
updated-dependencies:
- dependency-name: vmactions/freebsd-vm
  dependency-version: 1.3.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-29 18:18:55 +01:00
dependabot[bot]
80d058af10 Bump vmactions/openbsd-vm from 1.2.9 to 1.3.1
Bumps [vmactions/openbsd-vm](https://github.com/vmactions/openbsd-vm) from 1.2.9 to 1.3.1.
- [Release notes](https://github.com/vmactions/openbsd-vm/releases)
- [Commits](https://github.com/vmactions/openbsd-vm/compare/v1.2.9...v1.3.1)

---
updated-dependencies:
- dependency-name: vmactions/openbsd-vm
  dependency-version: 1.3.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-29 17:23:16 +01:00
19 changed files with 1070 additions and 126 deletions

View File

@@ -747,7 +747,7 @@ jobs:
df -h
- name: Build FreeBSD
id: build-freebsd
uses: vmactions/freebsd-vm@v1.3.2
uses: vmactions/freebsd-vm@v1.3.5
with:
usesh: true
mem: 8192
@@ -772,7 +772,7 @@ jobs:
submodules: recursive
- name: Build OpenBSD
id: build-openbsd
uses: vmactions/openbsd-vm@v1.2.9
uses: vmactions/openbsd-vm@v1.3.1
with:
usesh: true
mem: 4096
@@ -845,7 +845,7 @@ jobs:
p12-password: ${{ secrets.APPLE_DEVELOPER_ID_CERTIFICATE_PASSWORD }}
- name: Download macOS dependencies
run: curl -f -O -L https://github.com/strawberrymusicplayer/strawberry-macos-dependencies$(test "${{env.arch}}" = "x86_64" && echo "-intel" || echo "")/releases/latest/download/strawberry-macos-${{env.arch}}-${{env.buildtype}}.tar.xz
run: curl -f -O -L https://github.com/strawberrymusicplayer/strawberry-macos-dependencies/releases/latest/download/strawberry-macos-${{env.arch}}-${{env.buildtype}}.tar.xz
- name: Extract macOS dependencies
run: sudo tar -C / -xf strawberry-macos-${{env.arch}}-${{env.buildtype}}.tar.xz
@@ -898,7 +898,7 @@ jobs:
- name: Manually Codesign
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && matrix.runner == 'macos-15-intel'
working-directory: build
run: codesign -s 383J84DVB6 -f strawberry.app/Contents/Frameworks/{libpcre2-8.0.dylib,libpcre2-16.0.dylib,libpng16.16.dylib,libfreetype.6.dylib,libzstd.1.dylib,libbrotlicommon.1.dylib,libbrotlienc.1.dylib} strawberry.app/Contents/Frameworks/png.framework/png strawberry.app
run: codesign -s 383J84DVB6 -f strawberry.app/Contents/Frameworks/{libpcre2-8.0.dylib,libpcre2-16.0.dylib,libpng16.16.dylib,libfreetype.6.dylib,libzstd.1.dylib,libbrotlicommon.1.dylib,libbrotlienc.1.dylib,libbrotlidec.1.dylib,libsoup-3.0.0.dylib,libnghttp2.14.dylib,libpsl.5.dylib} strawberry.app/Contents/Frameworks/png.framework/png strawberry.app
- name: Manually Codesign
if: github.repository == 'strawberrymusicplayer/strawberry' && github.event.pull_request.head.repo.fork == false && matrix.runner == 'macos-15'

View File

@@ -823,6 +823,8 @@ set(SOURCES
src/fileview/fileview.cpp
src/fileview/fileviewlist.cpp
src/fileview/fileviewtree.cpp
src/fileview/fileviewtreemodel.cpp
src/device/devicemanager.cpp
src/device/devicelister.cpp
@@ -1112,6 +1114,8 @@ set(HEADERS
src/fileview/fileview.h
src/fileview/fileviewlist.h
src/fileview/fileviewtree.h
src/fileview/fileviewtreemodel.h
src/device/devicemanager.h
src/device/devicelister.h

View File

@@ -383,7 +383,7 @@ void CollectionView::keyPressEvent(QKeyEvent *e) {
case Qt::Key_Enter:
case Qt::Key_Return:
if (currentIndex().isValid()) {
AddToPlaylist();
Q_EMIT doubleClicked(currentIndex());
}
e->accept();
break;

View File

@@ -447,6 +447,14 @@ MainWindow::MainWindow(Application *app,
ui_->tabs->SetBackgroundPixmap(QPixmap(u":/pictures/sidebar-background.png"_s));
ui_->tabs->LoadSettings(QLatin1String(MainWindowSettings::kSettingsGroup));
// Save tab mode immediately when changed to avoid losing the setting
QObject::connect(ui_->tabs, &FancyTabWidget::ModeChanged, this, [this](FancyTabWidget::Mode mode) {
Settings s;
s.beginGroup(MainWindowSettings::kSettingsGroup);
s.setValue("tab_mode", static_cast<int>(mode));
s.endGroup();
});
track_position_timer_->setInterval(kTrackPositionUpdateTimeMs);
QObject::connect(track_position_timer_, &QTimer::timeout, this, &MainWindow::UpdateTrackPosition);
track_slider_timer_->setInterval(kTrackSliderUpdateTimeMs);

View File

@@ -43,7 +43,6 @@
#include <QString>
#include <QUrl>
#include <QImage>
#include <QPixmap>
#include <QTimer>
#include <QtEvents>
@@ -290,9 +289,6 @@ class MainWindow : public QMainWindow, public PlatformInterface {
void CheckFullRescanRevisions();
// creates the icon by painting the full one depending on the current position
QPixmap CreateOverlayedIcon(const int position, const int scrobble_point);
void GetCoverAutomatically();
void SetToggleScrobblingIcon(const bool value);

View File

@@ -1668,12 +1668,24 @@ void Song::InitArtManual() {
void Song::InitArtAutomatic() {
if (d->art_automatic_.isEmpty() && d->source_ == Source::LocalFile && d->url_.isLocalFile()) {
// Pick the first image file in the album directory.
QFileInfo file(d->url_.toLocalFile());
QDir dir(file.path());
QStringList files = dir.entryList(QStringList() << u"*.jpg"_s << u"*.png"_s << u"*.gif"_s << u"*.jpeg"_s, QDir::Files|QDir::Readable, QDir::Name);
if (files.count() > 0) {
d->art_automatic_ = QUrl::fromLocalFile(file.path() + QDir::separator() + files.first());
const QFileInfo fileinfo(d->url_.toLocalFile());
const QDir dir(fileinfo.path());
const QStringList cover_files = dir.entryList(QStringList() << u"*.jpg"_s << u"*.png"_s << u"*.gif"_s << u"*.jpeg"_s, QDir::Files|QDir::Readable, QDir::Name);
QString best_cover_file;
for (const QString &cover_file : cover_files) {
if (cover_file.contains("back"_L1, Qt::CaseInsensitive)) {
continue;
}
if (cover_file.contains("front"_L1, Qt::CaseInsensitive) || cover_file.startsWith("cover"_L1, Qt::CaseInsensitive)) {
best_cover_file = cover_file;
break;
}
if (best_cover_file.isEmpty()) {
best_cover_file = cover_file;
}
}
if (!best_cover_file.isEmpty()) {
d->art_automatic_ = QUrl::fromLocalFile(fileinfo.path() + QDir::separator() + best_cover_file);
}
}

View File

@@ -29,13 +29,17 @@
#include <QString>
#include <QStringList>
#include <QUrl>
#include <QSettings>
#include <QStandardPaths>
#include <QMessageBox>
#include <QScrollBar>
#include <QLineEdit>
#include <QToolButton>
#include <QFileDialog>
#include <QSpacerItem>
#include <QtEvents>
#include "constants/appearancesettings.h"
#include "constants/filefilterconstants.h"
#include "includes/shared_ptr.h"
#include "core/deletefiles.h"
#include "core/filesystemmusicstorage.h"
@@ -45,10 +49,11 @@
#include "dialogs/deleteconfirmationdialog.h"
#include "fileview.h"
#include "fileviewlist.h"
#include "fileviewtree.h"
#include "fileviewtreemodel.h"
#include "fileviewtreeitem.h"
#include "ui_fileview.h"
#include "organize/organizeerrordialog.h"
#include "constants/appearancesettings.h"
#include "constants/filefilterconstants.h"
using std::make_unique;
using namespace Qt::Literals::StringLiterals;
@@ -57,9 +62,12 @@ FileView::FileView(QWidget *parent)
: QWidget(parent),
ui_(new Ui_FileView),
model_(nullptr),
tree_model_(nullptr),
undo_stack_(new QUndoStack(this)),
task_manager_(nullptr),
storage_(new FilesystemMusicStorage(Song::Source::LocalFile, u"/"_s)) {
storage_(new FilesystemMusicStorage(Song::Source::LocalFile, u"/"_s)),
tree_view_active_(false),
view_mode_spacer_(nullptr) {
ui_->setupUi(this);
@@ -68,12 +76,14 @@ FileView::FileView(QWidget *parent)
ui_->forward->setIcon(IconLoader::Load(u"go-next"_s));
ui_->home->setIcon(IconLoader::Load(u"go-home"_s));
ui_->up->setIcon(IconLoader::Load(u"go-up"_s));
ui_->toggle_view->setIcon(IconLoader::Load(u"view-choose"_s));
QObject::connect(ui_->back, &QToolButton::clicked, undo_stack_, &QUndoStack::undo);
QObject::connect(ui_->forward, &QToolButton::clicked, undo_stack_, &QUndoStack::redo);
QObject::connect(ui_->home, &QToolButton::clicked, this, &FileView::FileHome);
QObject::connect(ui_->up, &QToolButton::clicked, this, &FileView::FileUp);
QObject::connect(ui_->path, &QLineEdit::textChanged, this, &FileView::ChangeFilePath);
QObject::connect(ui_->toggle_view, &QToolButton::clicked, this, &FileView::ToggleViewMode);
QObject::connect(undo_stack_, &QUndoStack::canUndoChanged, ui_->back, &FileView::setEnabled);
QObject::connect(undo_stack_, &QUndoStack::canRedoChanged, ui_->forward, &FileView::setEnabled);
@@ -87,6 +97,22 @@ FileView::FileView(QWidget *parent)
QObject::connect(ui_->list, &FileViewList::Delete, this, &FileView::Delete);
QObject::connect(ui_->list, &FileViewList::EditTags, this, &FileView::EditTags);
// Connect tree view signals
QObject::connect(ui_->tree, &FileViewTree::AddToPlaylist, this, &FileView::AddToPlaylist);
QObject::connect(ui_->tree, &FileViewTree::CopyToCollection, this, &FileView::CopyToCollection);
QObject::connect(ui_->tree, &FileViewTree::MoveToCollection, this, &FileView::MoveToCollection);
QObject::connect(ui_->tree, &FileViewTree::CopyToDevice, this, &FileView::CopyToDevice);
QObject::connect(ui_->tree, &FileViewTree::Delete, this, &FileView::Delete);
QObject::connect(ui_->tree, &FileViewTree::EditTags, this, &FileView::EditTags);
QObject::connect(ui_->tree, &FileViewTree::activated, this, &FileView::ItemActivated);
QObject::connect(ui_->tree, &FileViewTree::doubleClicked, this, &FileView::ItemDoubleClick);
// Setup tree root management buttons
ui_->add_tree_root->setIcon(IconLoader::Load(u"folder-new"_s));
ui_->remove_tree_root->setIcon(IconLoader::Load(u"list-remove"_s));
QObject::connect(ui_->add_tree_root, &QToolButton::clicked, this, &FileView::AddRootButtonClicked);
QObject::connect(ui_->remove_tree_root, &QToolButton::clicked, this, &FileView::RemoveRootButtonClicked);
QString filter = QLatin1String(kFileFilter);
filter_list_ << filter.split(u' ');
@@ -109,6 +135,19 @@ void FileView::ReloadSettings() {
ui_->forward->setIconSize(QSize(iconsize, iconsize));
ui_->home->setIconSize(QSize(iconsize, iconsize));
ui_->up->setIconSize(QSize(iconsize, iconsize));
ui_->toggle_view->setIconSize(QSize(iconsize, iconsize));
ui_->add_tree_root->setIconSize(QSize(iconsize, iconsize));
ui_->remove_tree_root->setIconSize(QSize(iconsize, iconsize));
// Load tree root paths setting
Settings file_settings;
file_settings.beginGroup(u"FileView"_s);
tree_root_paths_ = file_settings.value(u"tree_root_paths"_s, QStandardPaths::standardLocations(QStandardPaths::StandardLocation::MusicLocation)).toStringList();
tree_view_active_ = file_settings.value(u"tree_view_active"_s, false).toBool();
file_settings.endGroup();
// Set initial view mode
UpdateViewModeUI();
}
@@ -180,24 +219,46 @@ void FileView::ChangeFilePathWithoutUndo(const QString &new_path) {
}
void FileView::ItemActivated(const QModelIndex &idx) {
if (model_->isDir(idx))
// Only handle activation for list view (not tree view)
if (!tree_view_active_ && model_->isDir(idx)) {
ChangeFilePath(model_->filePath(idx));
}
}
void FileView::ItemDoubleClick(const QModelIndex &idx) {
if (model_->isDir(idx)) {
return;
QString file_path;
bool is_file = false;
// Handle tree view with virtual roots
if (tree_view_active_ && tree_model_) {
QVariant type_var = tree_model_->data(idx, FileViewTreeModel::Role_Type);
if (type_var.isValid()) {
FileViewTreeItem::Type item_type = type_var.value<FileViewTreeItem::Type>();
// Only handle files, ignore directories and virtual roots
if (item_type == FileViewTreeItem::Type::File) {
file_path = tree_model_->data(idx, FileViewTreeModel::Role_FilePath).toString();
is_file = true;
}
}
}
// Handle list view with filesystem model
else if (!tree_view_active_ && model_) {
if (!model_->isDir(idx)) {
file_path = model_->filePath(idx);
is_file = true;
}
}
QString file_path = model_->filePath(idx);
// Add file to playlist if it's a valid file
if (is_file && !file_path.isEmpty()) {
MimeData *mimedata = new MimeData;
mimedata->from_doubleclick_ = true;
mimedata->setUrls(QList<QUrl>() << QUrl::fromLocalFile(file_path));
mimedata->name_for_new_playlist_ = file_path;
MimeData *mimedata = new MimeData;
mimedata->from_doubleclick_ = true;
mimedata->setUrls(QList<QUrl>() << QUrl::fromLocalFile(file_path));
mimedata->name_for_new_playlist_ = file_path;
Q_EMIT AddToPlaylist(mimedata);
Q_EMIT AddToPlaylist(mimedata);
}
}
@@ -272,12 +333,156 @@ void FileView::showEvent(QShowEvent *e) {
model_->setNameFilterDisables(false);
ui_->list->setModel(model_);
// Create tree model
tree_model_ = new FileViewTreeModel(this);
tree_model_->SetNameFilters(filter_list_);
SetupTreeView();
ChangeFilePathWithoutUndo(QDir::homePath());
if (!lazy_set_path_.isEmpty()) ChangeFilePathWithoutUndo(lazy_set_path_);
}
void FileView::SetupTreeView() {
// Use the new tree model with virtual roots
ui_->tree->setModel(tree_model_);
// Set the root paths in the model
tree_model_->SetRootPaths(tree_root_paths_);
// No need to set root index - the model handles virtual roots
}
void FileView::ToggleViewMode() {
tree_view_active_ = !tree_view_active_;
UpdateViewModeUI();
// Save the preference
Settings s;
s.beginGroup(u"FileView"_s);
s.setValue(u"tree_view_active"_s, tree_view_active_);
s.endGroup();
}
void FileView::UpdateViewModeUI() {
if (tree_view_active_) {
ui_->view_stack->setCurrentWidget(ui_->tree_page);
// Hide navigation controls in tree view mode
ui_->back->setVisible(false);
ui_->forward->setVisible(false);
ui_->up->setVisible(false);
ui_->home->setVisible(false);
ui_->path->setVisible(false);
// Show tree root management buttons
ui_->add_tree_root->setVisible(true);
ui_->remove_tree_root->setVisible(true);
// Insert spacer in tree view if not already present
if (!view_mode_spacer_) {
view_mode_spacer_ = new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum);
ui_->horizontalLayout->insertSpacerItem(ui_->horizontalLayout->indexOf(ui_->toggle_view), view_mode_spacer_);
}
}
else {
ui_->view_stack->setCurrentWidget(ui_->list_page);
// Show navigation controls in list view mode
ui_->back->setVisible(true);
ui_->forward->setVisible(true);
ui_->up->setVisible(true);
ui_->home->setVisible(true);
ui_->path->setVisible(true);
// Hide tree root management buttons in list view
ui_->add_tree_root->setVisible(false);
ui_->remove_tree_root->setVisible(false);
// Remove spacer in list view
if (view_mode_spacer_) {
ui_->horizontalLayout->removeItem(view_mode_spacer_);
delete view_mode_spacer_;
view_mode_spacer_ = nullptr;
}
}
}
void FileView::AddTreeRootPath(const QString &path) {
if (!tree_root_paths_.contains(path)) {
tree_root_paths_.append(path);
SaveTreeRootPaths();
// Refresh the tree view to show the new root
if (tree_model_) {
SetupTreeView();
}
}
}
void FileView::RemoveTreeRootPath(const QString &path) {
tree_root_paths_.removeAll(path);
SaveTreeRootPaths();
// Refresh the tree view
if (tree_model_) {
SetupTreeView();
}
}
void FileView::SaveTreeRootPaths() {
Settings s;
s.beginGroup(u"FileView"_s);
s.setValue(u"tree_root_paths"_s, tree_root_paths_);
s.endGroup();
}
void FileView::AddRootButtonClicked() {
const QString dir = QFileDialog::getExistingDirectory(this, tr("Select folder to add as tree root"), tree_root_paths_.isEmpty() ? QDir::homePath() : tree_root_paths_.first(), QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
if (!dir.isEmpty()) {
AddTreeRootPath(dir);
}
}
void FileView::RemoveRootButtonClicked() {
// Get currently selected item in tree
QModelIndex current = ui_->tree->currentIndex();
if (!current.isValid()) return;
QString path;
// Get the file path from the appropriate model
if (tree_model_) {
path = tree_model_->data(current, FileViewTreeModel::Role_FilePath).toString();
}
if (path.isEmpty()) return;
const QString clean_path = QDir::cleanPath(path);
// Check if this path or any parent is a configured root
for (const QString &root : std::as_const(tree_root_paths_)) {
const QString clean_root = QDir::cleanPath(root);
if (clean_path == clean_root || clean_path.startsWith(clean_root + QDir::separator())) {
RemoveTreeRootPath(root);
return;
}
}
}
void FileView::keyPressEvent(QKeyEvent *e) {
switch (e->key()) {

View File

@@ -40,10 +40,12 @@ class QFileIconProvider;
class QUndoStack;
class QKeyEvent;
class QShowEvent;
class QSpacerItem;
class MusicStorage;
class TaskManager;
class Ui_FileView;
class FileViewTreeModel;
class FileView : public QWidget {
Q_OBJECT
@@ -76,12 +78,22 @@ class FileView : public QWidget {
void ChangeFilePath(const QString &new_path);
void ItemActivated(const QModelIndex &idx);
void ItemDoubleClick(const QModelIndex &idx);
void ToggleViewMode();
void Delete(const QStringList &filenames);
void DeleteFinished(const SongList &songs_with_errors);
public Q_SLOTS:
void AddTreeRootPath(const QString &path);
void RemoveTreeRootPath(const QString &path);
private:
void ChangeFilePathWithoutUndo(const QString &new_path);
void SetupTreeView();
void SaveTreeRootPaths();
void AddRootButtonClicked();
void RemoveRootButtonClicked();
void UpdateViewModeUI();
private:
class UndoCommand : public QUndoCommand {
@@ -110,16 +122,21 @@ class FileView : public QWidget {
Ui_FileView *ui_;
QFileSystemModel *model_;
FileViewTreeModel *tree_model_;
QUndoStack *undo_stack_;
SharedPtr<TaskManager> task_manager_;
SharedPtr<MusicStorage> storage_;
QString lazy_set_path_;
QStringList tree_root_paths_;
QStringList filter_list_;
ScopedPtr<QFileIconProvider> file_icon_provider_;
bool tree_view_active_;
QSpacerItem *view_mode_spacer_;
};
#endif // FILEVIEW_H

View File

@@ -95,28 +95,143 @@
<item>
<widget class="QLineEdit" name="path"/>
</item>
<item>
<widget class="QToolButton" name="add_tree_root">
<property name="toolTip">
<string>Add root directory</string>
</property>
<property name="iconSize">
<size>
<width>22</width>
<height>22</height>
</size>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="remove_tree_root">
<property name="toolTip">
<string>Remove selected root directory</string>
</property>
<property name="iconSize">
<size>
<width>22</width>
<height>22</height>
</size>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="toggle_view">
<property name="iconSize">
<size>
<width>22</width>
<height>22</height>
</size>
</property>
<property name="autoRaise">
<bool>true</bool>
</property>
<property name="toolTip">
<string>Toggle between list and tree view</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="FileViewList" name="list">
<property name="dragEnabled">
<bool>true</bool>
</property>
<property name="dragDropMode">
<enum>QAbstractItemView::DragOnly</enum>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="iconSize">
<size>
<width>16</width>
<height>16</height>
</size>
<widget class="QStackedWidget" name="view_stack">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="list_page">
<layout class="QVBoxLayout" name="list_layout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="FileViewList" name="list">
<property name="dragEnabled">
<bool>true</bool>
</property>
<property name="dragDropMode">
<enum>QAbstractItemView::DragOnly</enum>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="iconSize">
<size>
<width>16</width>
<height>16</height>
</size>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tree_page">
<layout class="QVBoxLayout" name="tree_layout">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="FileViewTree" name="tree">
<property name="dragEnabled">
<bool>true</bool>
</property>
<property name="dragDropMode">
<enum>QAbstractItemView::DragOnly</enum>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<property name="iconSize">
<size>
<width>16</width>
<height>16</height>
</size>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
@@ -127,6 +242,11 @@
<extends>QListView</extends>
<header>fileview/fileviewlist.h</header>
</customwidget>
<customwidget>
<class>FileViewTree</class>
<extends>QTreeView</extends>
<header>fileview/fileviewtree.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>

View File

@@ -99,7 +99,7 @@ MimeData *FileViewList::MimeDataFromSelection() const {
const QStringList filenames = FilenamesFromSelection();
// if just one folder selected - use its path as the new playlist's name
// If just one folder selected - use its path as the new playlist's name
if (filenames.size() == 1 && QFileInfo(filenames.first()).isDir()) {
if (filenames.first().length() > 20) {
mimedata->name_for_new_playlist_ = QDir(filenames.first()).dirName();
@@ -108,7 +108,7 @@ MimeData *FileViewList::MimeDataFromSelection() const {
mimedata->name_for_new_playlist_ = filenames.first();
}
}
// otherwise, use the current root path
// Otherwise, use the current root path
else {
QString path = qobject_cast<QFileSystemModel*>(model())->rootPath();
if (path.length() > 20) {
@@ -196,11 +196,11 @@ void FileViewList::mousePressEvent(QMouseEvent *e) {
case Qt::XButton2:
Q_EMIT Forward();
break;
// enqueue to playlist with middleClick
// Enqueue to playlist with middleClick
case Qt::MiddleButton:{
QListView::mousePressEvent(e);
// we need to update the menu selection
// We need to update the menu selection
menu_selection_ = selectionModel()->selection();
MimeData *mimedata = new MimeData;

View File

@@ -0,0 +1,205 @@
/*
* Strawberry Music Player
* Copyright 2025, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <algorithm>
#include <utility>
#include <QWidget>
#include <QAbstractItemModel>
#include <QFileInfo>
#include <QDir>
#include <QMenu>
#include <QUrl>
#include <QCollator>
#include <QtEvents>
#include "core/iconloader.h"
#include "core/mimedata.h"
#include "utilities/filemanagerutils.h"
#include "fileviewtree.h"
#include "fileviewtreemodel.h"
using namespace Qt::Literals::StringLiterals;
FileViewTree::FileViewTree(QWidget *parent)
: QTreeView(parent),
menu_(new QMenu(this)) {
menu_->addAction(IconLoader::Load(u"media-playback-start"_s), tr("Append to current playlist"), this, &FileViewTree::AddToPlaylistSlot);
menu_->addAction(IconLoader::Load(u"media-playback-start"_s), tr("Replace current playlist"), this, &FileViewTree::LoadSlot);
menu_->addAction(IconLoader::Load(u"document-new"_s), tr("Open in new playlist"), this, &FileViewTree::OpenInNewPlaylistSlot);
menu_->addSeparator();
menu_->addAction(IconLoader::Load(u"edit-copy"_s), tr("Copy to collection..."), this, &FileViewTree::CopyToCollectionSlot);
menu_->addAction(IconLoader::Load(u"go-jump"_s), tr("Move to collection..."), this, &FileViewTree::MoveToCollectionSlot);
menu_->addAction(IconLoader::Load(u"device"_s), tr("Copy to device..."), this, &FileViewTree::CopyToDeviceSlot);
menu_->addAction(IconLoader::Load(u"edit-delete"_s), tr("Delete from disk..."), this, &FileViewTree::DeleteSlot);
menu_->addSeparator();
menu_->addAction(IconLoader::Load(u"edit-rename"_s), tr("Edit track information..."), this, &FileViewTree::EditTagsSlot);
menu_->addAction(IconLoader::Load(u"document-open-folder"_s), tr("Show in file browser..."), this, &FileViewTree::ShowInBrowser);
setAttribute(Qt::WA_MacShowFocusRect, false);
setHeaderHidden(true);
setUniformRowHeights(true);
}
void FileViewTree::contextMenuEvent(QContextMenuEvent *e) {
menu_selection_ = selectionModel()->selection();
menu_->popup(e->globalPos());
e->accept();
}
QStringList FileViewTree::FilenamesFromSelection() const {
QStringList filenames;
const QModelIndexList indexes = menu_selection_.indexes();
FileViewTreeModel *tree_model = qobject_cast<FileViewTreeModel*>(model());
if (tree_model) {
for (const QModelIndex &index : indexes) {
if (index.column() == 0) {
QString path = tree_model->data(index, FileViewTreeModel::Role_FilePath).toString();
if (!path.isEmpty()) {
filenames << path;
}
}
}
}
QCollator collator;
collator.setNumericMode(true);
std::sort(filenames.begin(), filenames.end(), collator);
return filenames;
}
QList<QUrl> FileViewTree::UrlListFromSelection() const {
QList<QUrl> urls;
const QStringList filenames = FilenamesFromSelection();
urls.reserve(filenames.count());
for (const QString &filename : std::as_const(filenames)) {
urls << QUrl::fromLocalFile(filename);
}
return urls;
}
MimeData *FileViewTree::MimeDataFromSelection() const {
MimeData *mimedata = new MimeData;
mimedata->setUrls(UrlListFromSelection());
const QStringList filenames = FilenamesFromSelection();
// if just one folder selected - use its path as the new playlist's name
if (filenames.size() == 1 && QFileInfo(filenames.first()).isDir()) {
if (filenames.first().length() > 20) {
mimedata->name_for_new_playlist_ = QDir(filenames.first()).dirName();
}
else {
mimedata->name_for_new_playlist_ = filenames.first();
}
}
// otherwise, use "Files" as default
else {
mimedata->name_for_new_playlist_ = tr("Files");
}
return mimedata;
}
void FileViewTree::LoadSlot() {
MimeData *mimedata = MimeDataFromSelection();
mimedata->clear_first_ = true;
Q_EMIT AddToPlaylist(mimedata);
}
void FileViewTree::AddToPlaylistSlot() {
Q_EMIT AddToPlaylist(MimeDataFromSelection());
}
void FileViewTree::OpenInNewPlaylistSlot() {
MimeData *mimedata = MimeDataFromSelection();
mimedata->open_in_new_playlist_ = true;
Q_EMIT AddToPlaylist(mimedata);
}
void FileViewTree::CopyToCollectionSlot() {
Q_EMIT CopyToCollection(UrlListFromSelection());
}
void FileViewTree::MoveToCollectionSlot() {
Q_EMIT MoveToCollection(UrlListFromSelection());
}
void FileViewTree::CopyToDeviceSlot() {
Q_EMIT CopyToDevice(UrlListFromSelection());
}
void FileViewTree::DeleteSlot() {
Q_EMIT Delete(FilenamesFromSelection());
}
void FileViewTree::EditTagsSlot() {
Q_EMIT EditTags(UrlListFromSelection());
}
void FileViewTree::mousePressEvent(QMouseEvent *e) {
switch (e->button()) {
// Enqueue to playlist with middleClick
case Qt::MiddleButton:{
QTreeView::mousePressEvent(e);
// We need to update the menu selection
QItemSelectionModel *selection_model = selectionModel();
if (!selection_model) {
e->ignore();
return;
}
menu_selection_ = selection_model->selection();
MimeData *mimedata = new MimeData;
mimedata->setUrls(UrlListFromSelection());
mimedata->enqueue_now_ = true;
Q_EMIT AddToPlaylist(mimedata);
break;
}
default:
QTreeView::mousePressEvent(e);
break;
}
}
void FileViewTree::ShowInBrowser() {
Utilities::OpenInFileBrowser(UrlListFromSelection());
}

View File

@@ -0,0 +1,78 @@
/*
* Strawberry Music Player
* Copyright 2025, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef FILEVIEWTREE_H
#define FILEVIEWTREE_H
#include <QObject>
#include <QTreeView>
#include <QList>
#include <QUrl>
#include <QString>
#include <QStringList>
class QWidget;
class QMimeData;
class QMenu;
class QMouseEvent;
class QContextMenuEvent;
class MimeData;
class FileViewTree : public QTreeView {
Q_OBJECT
public:
explicit FileViewTree(QWidget *parent = nullptr);
void mousePressEvent(QMouseEvent *e) override;
Q_SIGNALS:
void AddToPlaylist(QMimeData *data);
void CopyToCollection(const QList<QUrl> &urls);
void MoveToCollection(const QList<QUrl> &urls);
void CopyToDevice(const QList<QUrl> &urls);
void Delete(const QStringList &filenames);
void EditTags(const QList<QUrl> &urls);
protected:
void contextMenuEvent(QContextMenuEvent *e) override;
private:
QStringList FilenamesFromSelection() const;
QList<QUrl> UrlListFromSelection() const;
MimeData *MimeDataFromSelection() const;
private Q_SLOTS:
void LoadSlot();
void AddToPlaylistSlot();
void OpenInNewPlaylistSlot();
void CopyToCollectionSlot();
void MoveToCollectionSlot();
void CopyToDeviceSlot();
void DeleteSlot();
void EditTagsSlot();
void ShowInBrowser();
private:
QMenu *menu_;
QItemSelection menu_selection_;
};
#endif // FILEVIEWTREE_H

View File

@@ -0,0 +1,52 @@
/*
* Strawberry Music Player
* Copyright 2025, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef FILEVIEWTREEITEM_H
#define FILEVIEWTREEITEM_H
#include "config.h"
#include <QFileInfo>
#include "core/simpletreeitem.h"
class FileViewTreeItem : public SimpleTreeItem<FileViewTreeItem> {
public:
enum class Type {
Root, // Hidden root
VirtualRoot, // User-configured root paths
Directory, // File system directory
File // File system file
};
explicit FileViewTreeItem(SimpleTreeModel<FileViewTreeItem> *_model) : SimpleTreeItem<FileViewTreeItem>(_model), type(Type::Root), lazy_loaded(false) {}
explicit FileViewTreeItem(const Type _type, FileViewTreeItem *_parent = nullptr) : SimpleTreeItem<FileViewTreeItem>(_parent), type(_type), lazy_loaded(false) {}
Type type;
QString file_path; // Absolute file system path
QFileInfo file_info; // Cached file info
bool lazy_loaded; // Whether children have been loaded
private:
Q_DISABLE_COPY(FileViewTreeItem)
};
Q_DECLARE_METATYPE(FileViewTreeItem::Type)
#endif // FILEVIEWTREEITEM_H

View File

@@ -0,0 +1,246 @@
/*
* Strawberry Music Player
* Copyright 2025, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <QObject>
#include <QVariant>
#include <QString>
#include <QStringList>
#include <QList>
#include <QMap>
#include <QDir>
#include <QFileInfo>
#include <QFileIconProvider>
#include <QMimeData>
#include <QUrl>
#include <QIcon>
#include "core/simpletreemodel.h"
#include "core/logging.h"
#include "fileviewtreemodel.h"
#include "fileviewtreeitem.h"
using namespace Qt::Literals::StringLiterals;
FileViewTreeModel::FileViewTreeModel(QObject *parent)
: SimpleTreeModel<FileViewTreeItem>(new FileViewTreeItem(this), parent),
icon_provider_(new QFileIconProvider()) {
}
FileViewTreeModel::~FileViewTreeModel() {
delete root_;
delete icon_provider_;
}
Qt::ItemFlags FileViewTreeModel::flags(const QModelIndex &idx) const {
const FileViewTreeItem *item = IndexToItem(idx);
if (!item) return Qt::NoItemFlags;
switch (item->type) {
case FileViewTreeItem::Type::VirtualRoot:
case FileViewTreeItem::Type::Directory:
case FileViewTreeItem::Type::File:
return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled;
case FileViewTreeItem::Type::Root:
default:
return Qt::ItemIsEnabled;
}
}
QVariant FileViewTreeModel::data(const QModelIndex &idx, const int role) const {
if (!idx.isValid()) return QVariant();
const FileViewTreeItem *item = IndexToItem(idx);
if (!item) return QVariant();
switch (role) {
case Qt::DisplayRole:
if (item->type == FileViewTreeItem::Type::VirtualRoot) {
return item->display_text.isEmpty() ? item->file_path : item->display_text;
}
return item->file_info.fileName();
case Qt::DecorationRole:
return GetIcon(item);
case Role_Type:
return QVariant::fromValue(item->type);
case Role_FilePath:
return item->file_path;
case Role_FileName:
return item->file_info.fileName();
default:
return QVariant();
}
}
bool FileViewTreeModel::hasChildren(const QModelIndex &parent) const {
const FileViewTreeItem *item = IndexToItem(parent);
if (!item) return false;
// Root and VirtualRoot always have children (or can have them)
if (item->type == FileViewTreeItem::Type::Root) return true;
if (item->type == FileViewTreeItem::Type::VirtualRoot) return true;
// Directories can have children
if (item->type == FileViewTreeItem::Type::Directory) {
return true;
}
// Files don't have children
return false;
}
bool FileViewTreeModel::canFetchMore(const QModelIndex &parent) const {
const FileViewTreeItem *item = IndexToItem(parent);
if (!item) return false;
// Can fetch more if not yet lazy loaded
return !item->lazy_loaded && (item->type == FileViewTreeItem::Type::VirtualRoot || item->type == FileViewTreeItem::Type::Directory);
}
void FileViewTreeModel::fetchMore(const QModelIndex &parent) {
FileViewTreeItem *item = IndexToItem(parent);
if (!item || item->lazy_loaded) return;
LazyLoad(item);
}
void FileViewTreeModel::LazyLoad(FileViewTreeItem *item) {
if (item->lazy_loaded) return;
QDir dir(item->file_path);
if (!dir.exists()) {
item->lazy_loaded = true;
return;
}
// Apply name filters
const QDir::Filters filters = QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot;
if (!name_filters_.isEmpty()) {
dir.setNameFilters(name_filters_);
}
const QFileInfoList entries = dir.entryInfoList(filters, QDir::Name | QDir::DirsFirst);
if (!entries.isEmpty()) {
BeginInsert(item, 0, static_cast<int>(entries.count()) - 1);
for (const QFileInfo &entry : entries) {
FileViewTreeItem *child = new FileViewTreeItem(
entry.isDir() ? FileViewTreeItem::Type::Directory : FileViewTreeItem::Type::File,
item
);
child->file_path = entry.absoluteFilePath();
child->file_info = entry;
child->lazy_loaded = false;
child->display_text = entry.fileName();
}
EndInsert();
}
item->lazy_loaded = true;
}
QIcon FileViewTreeModel::GetIcon(const FileViewTreeItem *item) const {
if (!item) return QIcon();
switch (item->type) {
case FileViewTreeItem::Type::VirtualRoot:
case FileViewTreeItem::Type::Directory:
return icon_provider_->icon(QFileIconProvider::Folder);
case FileViewTreeItem::Type::File:
return icon_provider_->icon(item->file_info);
default:
return QIcon();
}
}
QStringList FileViewTreeModel::mimeTypes() const {
return QStringList() << u"text/uri-list"_s;
}
QMimeData *FileViewTreeModel::mimeData(const QModelIndexList &indexes) const {
if (indexes.isEmpty()) return nullptr;
QList<QUrl> urls;
for (const QModelIndex &idx : indexes) {
const FileViewTreeItem *item = IndexToItem(idx);
if (item && (item->type == FileViewTreeItem::Type::File || item->type == FileViewTreeItem::Type::Directory || item->type == FileViewTreeItem::Type::VirtualRoot)) {
urls << QUrl::fromLocalFile(item->file_path);
}
}
if (urls.isEmpty()) return nullptr;
QMimeData *data = new QMimeData();
data->setUrls(urls);
return data;
}
void FileViewTreeModel::SetRootPaths(const QStringList &paths) {
Reset();
for (const QString &path : paths) {
QFileInfo info(path);
if (!info.exists() || !info.isDir()) continue;
FileViewTreeItem *virtual_root = new FileViewTreeItem(FileViewTreeItem::Type::VirtualRoot, root_);
virtual_root->file_path = info.absoluteFilePath();
virtual_root->file_info = info;
virtual_root->display_text = info.absoluteFilePath();
virtual_root->lazy_loaded = false;
}
}
void FileViewTreeModel::SetNameFilters(const QStringList &filters) {
name_filters_ = filters;
}
void FileViewTreeModel::Reset() {
beginResetModel();
// Clear children without notifications since we're in a reset
qDeleteAll(root_->children);
root_->children.clear();
endResetModel();
}

View File

@@ -0,0 +1,72 @@
/*
* Strawberry Music Player
* Copyright 2025, Jonas Kvinge <jonas@jkvinge.net>
*
* Strawberry is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Strawberry is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Strawberry. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef FILEVIEWTREEMODEL_H
#define FILEVIEWTREEMODEL_H
#include "config.h"
#include <QObject>
#include <QVariant>
#include <QStringList>
#include <QIcon>
#include "core/simpletreemodel.h"
#include "fileviewtreeitem.h"
class QFileIconProvider;
class QMimeData;
class FileViewTreeModel : public SimpleTreeModel<FileViewTreeItem> {
Q_OBJECT
public:
explicit FileViewTreeModel(QObject *parent = nullptr);
~FileViewTreeModel() override;
enum Role {
Role_Type = Qt::UserRole + 1,
Role_FilePath,
Role_FileName,
RoleCount
};
// QAbstractItemModel
Qt::ItemFlags flags(const QModelIndex &idx) const override;
QVariant data(const QModelIndex &idx, const int role) const override;
bool hasChildren(const QModelIndex &parent) const override;
bool canFetchMore(const QModelIndex &parent) const override;
void fetchMore(const QModelIndex &parent) override;
QStringList mimeTypes() const override;
QMimeData *mimeData(const QModelIndexList &indexes) const override;
void SetRootPaths(const QStringList &paths);
void SetNameFilters(const QStringList &filters);
private:
void Reset();
void LazyLoad(FileViewTreeItem *item);
QIcon GetIcon(const FileViewTreeItem *item) const;
private:
QFileIconProvider *icon_provider_;
QStringList name_filters_;
};
#endif // FILEVIEWTREEMODEL_H

View File

@@ -22,9 +22,6 @@
#include <algorithm>
#include <cmath>
#include <glib-object.h>
#include <gst/gst.h>
#include <QList>
#include <QByteArray>
@@ -64,29 +61,6 @@ void MoodbarBuilder::Init(const int bands, const int rate_hz) {
}
void MoodbarBuilder::AddFrame(const GValue *magnitudes, const int size) {
if (size > barkband_table_.length()) {
return;
}
// Calculate total magnitudes for different bark bands.
double bands[sBarkBandCount]{};
for (int i = 0; i < size; ++i) {
const GValue *magnitude = gst_value_list_get_value(magnitudes, i);
bands[barkband_table_[i]] += g_value_get_float(magnitude);
}
// Now divide the bark bands into thirds and compute their total amplitudes.
double rgb[] = { 0, 0, 0 };
for (int i = 0; i < sBarkBandCount; ++i) {
rgb[(i * 3) / sBarkBandCount] += bands[i] * bands[i];
}
frames_.append(Rgb(sqrt(rgb[0]), sqrt(rgb[1]), sqrt(rgb[2])));
}
void MoodbarBuilder::AddFrame(const double *magnitudes, const int size) {
if (size > barkband_table_.length()) {

View File

@@ -22,8 +22,6 @@
#ifndef MOODBARBUILDER_H
#define MOODBARBUILDER_H
#include <glib-object.h>
#include <QtGlobal>
#include <QList>
#include <QByteArray>
@@ -33,7 +31,6 @@ class MoodbarBuilder {
explicit MoodbarBuilder();
void Init(const int bands, const int rate_hz);
void AddFrame(const GValue *magnitudes, const int size);
void AddFrame(const double *magnitudes, const int size);
QByteArray Finish(const int width);

View File

@@ -103,7 +103,7 @@ void MoodbarPipeline::Start() {
GstElement *decodebin = CreateElement("uridecodebin");
convert_element_ = CreateElement("audioconvert");
GstElement *spectrum = CreateElement("spectrum");
GstElement *spectrum = CreateElement("strawberry-fastspectrum");
GstElement *fakesink = CreateElement("fakesink");
if (!decodebin || !convert_element_ || !spectrum || !fakesink) {
@@ -122,7 +122,7 @@ void MoodbarPipeline::Start() {
return;
}
//builder_ = make_unique<MoodbarBuilder>();
builder_ = make_unique<MoodbarBuilder>();
// Set properties
@@ -130,16 +130,8 @@ void MoodbarPipeline::Start() {
g_object_set(decodebin, "uri", gst_url.constData(), nullptr);
g_object_set(spectrum, "bands", kBands, nullptr);
//GstStrawberryFastSpectrum *fastspectrum = reinterpret_cast<GstStrawberryFastSpectrum*>(spectrum);
//fastspectrum->output_callback = [this](double *magnitudes, const int size) { builder_->AddFrame(magnitudes, size); };
{
GstPad *pad = gst_element_get_static_pad(fakesink, "src");
if (pad) {
buffer_probe_cb_id_ = gst_pad_add_probe(pad, GST_PAD_PROBE_TYPE_BUFFER, BufferProbeCallback, this, nullptr);
gst_object_unref(pad);
}
}
GstStrawberryFastSpectrum *fastspectrum = reinterpret_cast<GstStrawberryFastSpectrum*>(spectrum);
fastspectrum->output_callback = [this](double *magnitudes, const int size) { builder_->AddFrame(magnitudes, size); };
// Connect signals
CHECKED_GCONNECT(decodebin, "pad-added", &NewPadCallback, this);
@@ -220,20 +212,6 @@ GstBusSyncReply MoodbarPipeline::BusCallbackSync(GstBus *bus, GstMessage *messag
if (!instance->running_) return GST_BUS_PASS;
switch (GST_MESSAGE_TYPE(message)) {
#if 0
case GST_MESSAGE_ELEMENT:{
const GstStructure *structure = gst_message_get_structure(message);
const gchar *name = gst_structure_get_name(structure);
if (strcmp(name, "spectrum") == 0) {
const GValue *magnitudes = gst_structure_get_value(structure, "magnitude");
if (instance->builder_) {
instance->builder_->AddFrame(magnitudes, kBands);
}
}
break;
}
#endif
case GST_MESSAGE_EOS:
instance->Stop(true);
break;
@@ -251,24 +229,6 @@ GstBusSyncReply MoodbarPipeline::BusCallbackSync(GstBus *bus, GstMessage *messag
}
GstPadProbeReturn MoodbarPipeline::BufferProbeCallback(GstPad *pad, GstPadProbeInfo *info, gpointer self) {
Q_UNUSED(pad)
MoodbarPipeline *instance = reinterpret_cast<MoodbarPipeline*>(self);
GstBuffer *buffer = gst_pad_probe_info_get_buffer(info);
GstMapInfo map;
gst_buffer_map(buffer, &map, GST_MAP_READ);
instance->data_.append(reinterpret_cast<const char*>(map.data), map.size);
gst_buffer_unmap(buffer, &map);
gst_buffer_unref(buffer);
return GST_PAD_PROBE_OK;
}
void MoodbarPipeline::Stop(const bool success) {
running_ = false;

View File

@@ -63,7 +63,6 @@ class MoodbarPipeline : public QObject {
static void NewPadCallback(GstElement *element, GstPad *pad, gpointer self);
static GstBusSyncReply BusCallbackSync(GstBus *bus, GstMessage *message, gpointer self);
static GstPadProbeReturn BufferProbeCallback(GstPad *pad, GstPadProbeInfo *info, gpointer self);
private:
QUrl url_;
@@ -75,7 +74,6 @@ class MoodbarPipeline : public QObject {
bool success_;
bool running_;
QByteArray data_;
gint buffer_probe_cb_id_;
};
using MoodbarPipelinePtr = QSharedPointer<MoodbarPipeline>;