diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 3666e5e..3688384 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -40,6 +40,10 @@ jobs: git clone --recurse-submodules -q https://github.com/taglib/taglib.git dependencies_src/taglib cmake .\dependencies_src\taglib -Bbuild_dependencies/taglib -DBUILD_SHARED_LIBS=ON -DCMAKE_INSTALL_PREFIX="dependencies_bin" || goto :error cmake --build build_dependencies/taglib --config Release --target=install -j || goto :error + :: ===== ECM ===== + git clone -q https://invent.kde.org/frameworks/extra-cmake-modules.git dependencies_src/extra-cmake-modules + cmake .\dependencies_src\extra-cmake-modules -Bbuild_dependencies/extra-cmake-modules -DCMAKE_INSTALL_PREFIX="dependencies_bin" -DBUILD_TESTING=OFF || goto :error + cmake --build build_dependencies/extra-cmake-modules --config Release --target=install || goto :error :: ------ app ------ cmake -Bbuild . -DCMAKE_INSTALL_PREFIX="%PWD%\build\" || goto :error cmake --build build --config Release -j || goto :error diff --git a/CMakeLists.txt b/CMakeLists.txt index 0bd1316..2e53dbd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,8 +14,14 @@ set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(Qt6 6.5.1 COMPONENTS Widgets Multimedia Network LinguistTools REQUIRED) +find_package(ECM 5.83.0 NO_MODULE) find_package(PkgConfig) +if (ECM_FOUND) + set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/cmake) + include(ECMAddAppIcon) +endif() + if (PKG_CONFIG_FOUND) pkg_check_modules(TagLib taglib IMPORTED_TARGET) endif () @@ -52,6 +58,14 @@ add_executable(${EXE_NAME} resources.qrc ) +if(ECM_FOUND) + ecm_add_app_icon(${EXE_NAME} + ICONS + dist/64-pineapple-music.png + dist/256-pineapple-music.png + ) +endif() + qt_add_translations(${EXE_NAME} TS_FILES ${PMUSIC_TS_FILES} @@ -86,15 +100,15 @@ elseif (UNIX) endif () # install icon - install ( + install( FILES icons/app-icon.svg DESTINATION "${CMAKE_INSTALL_DATADIR}/icons/hicolor/48x48/apps" - RENAME pineapple-music.svg + RENAME net.blumia.pineapple-music.svg ) # install shortcut - install ( - FILES pineapple-music.desktop + install( + FILES dist/net.blumia.pineapple-music.desktop DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/applications" ) endif() diff --git a/README.md b/README.md index 5a606f3..228d007 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,13 @@ -_**This is a not ready to use, toy project**_ +## Read Before Use Since **I** just need a simple player which *just works* right now, so I did many things in a dirty way. Don't feel so weird if you saw something I did in this project is using a bad approach. +### Feature Notice + +- File format support will be limited by the [FFmpeg version that Qt 6 uses](https://doc.qt.io/qt-6/qtmultimedia-attribution-ffmpeg.html). + - ...which if you use Qt's official binary, only contains the LGPLv2.1+ part. (already good enough, tho) +- No music library management support and there won't be one! + ## Build Current state, we need: @@ -13,27 +19,19 @@ Current state, we need: Then we can build it with any proper c++ compiler like g++ or msvc. -### Linux +Building it just requires normal cmake building steps: -Just normal build process as other program. Nothing special ;) - -### Windows - -Install the depts manually is a nightmare. I use [KDE Craft](https://community.kde.org/Craft) but MSYS2 should also works. FYI currently this project is not intended to works under Windows (it should works and I also did some simple test though). - -### macOS - -I don't have a mac, so no support at all. +```shell +$ cmake -Bbuild +$ cmake --build build +``` ## Help Translation! -TODO: move to Codeberg's Weblate. +[Translate this project on Codeberg's Weblate!](https://translate.codeberg.org/projects/pineapple-apps/pineapple-music/) ## About License -Since this is a toy repo, I don't spend much time about the license stuff. Currently this project use some assets and code from [ShadowPlayer](https://github.com/ShadowPower/ShadowPlayer), which have a very interesting license -- do whatever you want but cannot be used as homework -- obviously it's not a so called *free* license. I *may* do some license housecleaning works by replaceing the assets and code implementation when the code become reasonable, and the final codebase may probably released under MIT license. +Pineapple Music as a whole is licensed under MIT license. Individual files may have a different, but compatible license. -Anyway here is a list of file which is in non-free state (with license: do whatever you want but cannot be used as homework): - - - All png images inside `icons` folder. - - seekableslider.{h,cpp} +All *png* images inside `icons` folder are originally created by [@ShadowPower](https://github.com/ShadowPower/) for [ShadowPlayer](https://github.com/ShadowPower/ShadowPlayer). These images are licensed under [CC0](https://creativecommons.org/publicdomain/zero/1.0/legalcode) license, grant by the original author. diff --git a/dist/256-pineapple-music.png b/dist/256-pineapple-music.png new file mode 100644 index 0000000..87ef29e Binary files /dev/null and b/dist/256-pineapple-music.png differ diff --git a/dist/64-pineapple-music.png b/dist/64-pineapple-music.png new file mode 100644 index 0000000..daf330e Binary files /dev/null and b/dist/64-pineapple-music.png differ diff --git a/pineapple-music.desktop b/dist/net.blumia.pineapple-music.desktop similarity index 98% rename from pineapple-music.desktop rename to dist/net.blumia.pineapple-music.desktop index 2808c37..17757f8 100644 --- a/pineapple-music.desktop +++ b/dist/net.blumia.pineapple-music.desktop @@ -3,7 +3,7 @@ Categories=Audio;Player; Comment=Pineapple Music Audio Player. Exec=pmusic %F GenericName=Music -Icon=pineapple-music +Icon=net.blumia.pineapple-music Keywords=Picture;Image;Viewer;Jpg;Jpeg;Png; MimeType=application/ogg;application/x-ogg;audio/ogg;audio/vorbis;audio/x-vorbis;audio/x-vorbis+ogg;video/ogg;video/x-ogm;video/x-ogm+ogg;video/x-theora+ogg;video/x-theora;audio/x-speex;audio/opus;application/x-flac;audio/flac;audio/x-flac;audio/x-ms-asf;audio/x-ms-asx;audio/x-ms-wax;audio/x-ms-wma;video/x-ms-asf;video/x-ms-asf-plugin;video/x-ms-asx;video/x-ms-wm;video/x-ms-wmv;video/x-ms-wmx;video/x-ms-wvx;video/x-msvideo;audio/x-pn-windows-acm;video/divx;video/msvideo;video/vnd.divx;video/avi;video/x-avi;application/vnd.rn-realmedia;application/vnd.rn-realmedia-vbr;audio/vnd.rn-realaudio;audio/x-pn-realaudio;audio/x-pn-realaudio-plugin;audio/x-real-audio;audio/x-realaudio;video/vnd.rn-realvideo;audio/mpeg;audio/mpg;audio/mp1;audio/mp2;audio/mp3;audio/x-mp1;audio/x-mp2;audio/x-mp3;audio/x-mpeg;audio/x-mpg;video/mp2t;video/mpeg;video/mpeg-system;video/x-mpeg;video/x-mpeg2;video/x-mpeg-system;application/mpeg4-iod;application/mpeg4-muxcodetable;application/x-extension-m4a;application/x-extension-mp4;audio/aac;audio/m4a;audio/mp4;audio/x-m4a;audio/x-aac;video/mp4;video/mp4v-es;video/x-m4v;application/x-quicktime-media-link;application/x-quicktimeplayer;video/quicktime;application/x-matroska;audio/x-matroska;video/x-matroska;video/webm;audio/webm;audio/3gpp;audio/3gpp2;audio/AMR;audio/AMR-WB;video/3gp;video/3gpp;video/3gpp2;x-scheme-handler/mms;x-scheme-handler/mmsh;x-scheme-handler/rtsp;x-scheme-handler/rtp;x-scheme-handler/rtmp;x-scheme-handler/icy;x-scheme-handler/icyx;application/x-cd-image;x-content/video-vcd;x-content/video-svcd;x-content/video-dvd;x-content/audio-cdda;x-content/audio-player;application/ram;application/xspf+xml;audio/mpegurl;audio/x-mpegurl;audio/scpls;audio/x-scpls;text/google-video-pointer;text/x-google-video-pointer;video/vnd.mpegurl;application/vnd.apple.mpegurl;application/vnd.ms-asf;application/vnd.ms-wpl;application/sdp;audio/dv;video/dv;audio/x-aiff;audio/x-pn-aiff;video/x-anim;video/x-nsv;video/fli;video/flv;video/x-flc;video/x-fli;video/x-flv;audio/wav;audio/x-pn-au;audio/x-pn-wav;audio/x-wav;audio/x-adpcm;audio/ac3;audio/eac3;audio/vnd.dts;audio/vnd.dts.hd;audio/vnd.dolby.heaac.1;audio/vnd.dolby.heaac.2;audio/vnd.dolby.mlp;audio/basic;audio/midi;audio/x-ape;audio/x-gsm;audio/x-musepack;audio/x-tta;audio/x-wavpack;audio/x-shorten;application/x-shockwave-flash;application/x-flash-video;misc/ultravox;image/vnd.rn-realpix;audio/x-it;audio/x-mod;audio/x-s3m;audio/x-xm;application/mxf; Name=Pineapple Music diff --git a/main.cpp b/main.cpp index 4cd310c..73ac287 100644 --- a/main.cpp +++ b/main.cpp @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2024 Gary Wang +// +// SPDX-License-Identifier: MIT + #include "mainwindow.h" #include "singleapplicationmanager.h" diff --git a/mainwindow.cpp b/mainwindow.cpp index 4d4fa7c..3a6125b 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2024 Gary Wang +// +// SPDX-License-Identifier: MIT + #include "mainwindow.h" #include "./ui_mainwindow.h" @@ -59,7 +63,7 @@ void MainWindow::commandlinePlayAudioFiles(QStringList audioFiles) if (!audioFileUrls.isEmpty()) { QModelIndex modelIndex = m_playlistManager->loadPlaylist(audioFileUrls); if (modelIndex.isValid()) { - m_mediaPlayer->setSource(m_playlistManager->urlByIndex(modelIndex)); + loadByModelIndex(modelIndex); play(); } } @@ -194,9 +198,9 @@ void MainWindow::dropEvent(QDropEvent *e) return; } - QModelIndex modelIndex = m_playlistManager->loadPlaylist(urls); + const QModelIndex & modelIndex = m_playlistManager->loadPlaylist(urls); if (modelIndex.isValid()) { - m_mediaPlayer->setSource(m_playlistManager->urlByIndex(modelIndex)); + loadByModelIndex(modelIndex); play(); } } @@ -216,29 +220,14 @@ void MainWindow::loadFile() m_mediaPlayer->setSource(urlList.first()); } +void MainWindow::loadByModelIndex(const QModelIndex & index) +{ + m_mediaPlayer->setSource(m_playlistManager->urlByIndex(index)); +} + void MainWindow::play() { - QUrl fileUrl(m_mediaPlayer->source()); - m_mediaPlayer->play(); - - ui->titleLabel->setText(fileUrl.fileName()); - ui->titleLabel->setToolTip(fileUrl.fileName()); - - if (fileUrl.isLocalFile()) { - QString filePath(fileUrl.toLocalFile()); - QString suffix(filePath.mid(filePath.lastIndexOf('.') + 1)); - suffix = suffix.toUpper(); - -#ifndef NO_TAGLIB - TagLib::FileRef fileRef(filePath.toLocal8Bit().data()); - - if (!fileRef.isNull() && fileRef.audioProperties()) { - TagLib::AudioProperties *prop = fileRef.audioProperties(); - setAudioPropertyInfoForDisplay(prop->sampleRate(), prop->bitrate(), prop->channels(), suffix); - } -#endif // NO_TAGLIB - } } void MainWindow::centerWindow() @@ -323,7 +312,7 @@ void MainWindow::on_prevBtn_clicked() { QModelIndex index(m_playlistManager->previousIndex()); m_playlistManager->setCurrentIndex(index); - m_mediaPlayer->setSource(m_playlistManager->urlByIndex(index)); + loadByModelIndex(index); play(); } @@ -331,7 +320,7 @@ void MainWindow::on_nextBtn_clicked() { QModelIndex index(m_playlistManager->nextIndex()); m_playlistManager->setCurrentIndex(index); - m_mediaPlayer->setSource(m_playlistManager->urlByIndex(index)); + loadByModelIndex(index); play(); } @@ -362,6 +351,28 @@ void MainWindow::initUiAndAnimation() void MainWindow::initConnections() { + connect(m_mediaPlayer, &QMediaPlayer::sourceChanged, this, [=](){ + QUrl fileUrl(m_mediaPlayer->source()); + + ui->titleLabel->setText(fileUrl.fileName()); + ui->titleLabel->setToolTip(fileUrl.fileName()); + + if (fileUrl.isLocalFile()) { + QString filePath(fileUrl.toLocalFile()); + QString suffix(filePath.mid(filePath.lastIndexOf('.') + 1)); + suffix = suffix.toUpper(); + +#ifndef NO_TAGLIB + TagLib::FileRef fileRef(filePath.toLocal8Bit().data()); + + if (!fileRef.isNull() && fileRef.audioProperties()) { + TagLib::AudioProperties *prop = fileRef.audioProperties(); + setAudioPropertyInfoForDisplay(prop->sampleRate(), prop->bitrate(), prop->channels(), suffix); + } +#endif // NO_TAGLIB + } + }); + connect(m_mediaPlayer, &QMediaPlayer::metaDataChanged, this, [=](){ QMediaMetaData metadata(m_mediaPlayer->metaData()); setAudioMetadataForDisplay(metadata.stringValue(QMediaMetaData::Title), @@ -478,7 +489,7 @@ void MainWindow::on_playListBtn_clicked() void MainWindow::on_playlistView_activated(const QModelIndex &index) { m_playlistManager->setCurrentIndex(index); - m_mediaPlayer->setSource(m_playlistManager->urlByIndex(index)); + loadByModelIndex(index); play(); } diff --git a/mainwindow.h b/mainwindow.h index 3d72542..54929dd 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2024 Gary Wang +// +// SPDX-License-Identifier: MIT + #ifndef MAINWINDOW_H #define MAINWINDOW_H @@ -47,6 +51,7 @@ protected: void dropEvent(QDropEvent *e) override; void loadFile(); + void loadByModelIndex(const QModelIndex &index); void play(); void centerWindow(); diff --git a/mainwindow.ui b/mainwindow.ui index c76e4f2..8660b7a 100644 --- a/mainwindow.ui +++ b/mainwindow.ui @@ -115,7 +115,7 @@ QLabel#coverLabel { - 7 + 0 0 diff --git a/seekableslider.cpp b/seekableslider.cpp index 4d79ad4..e7bbf9d 100644 --- a/seekableslider.cpp +++ b/seekableslider.cpp @@ -1,29 +1,18 @@ -#include "seekableslider.h" - -SeekableSlider::SeekableSlider(QWidget *parent) : - QSlider(parent) -{ - //关闭分段移动 - setSingleStep(0); - setPageStep(0); -} - -//点击Slider即可调节Value -//只写了横向模式的Slider…… -void SeekableSlider::mousePressEvent(QMouseEvent *event) -{ - if (event->buttons() & Qt::LeftButton) { - QSlider::mousePressEvent(event); - double pos = event->pos().x() / (double)width(); - setValue(pos * (maximum() - minimum()) + minimum()); - } -} - -void SeekableSlider::mouseMoveEvent(QMouseEvent *event) -{ - if (event->buttons() & Qt::LeftButton) { - QSlider::mousePressEvent(event); - double pos = event->pos().x() / (double)width(); - setValue(pos * (maximum() - minimum()) + minimum()); - } -} +// SPDX-FileCopyrightText: 2024 Gary Wang +// +// SPDX-License-Identifier: MIT + +#include "seekableslider.h" + +SeekableSlider::SeekableSlider(QWidget *parent) : + QSlider(parent) +{ +} + +void SeekableSlider::mouseReleaseEvent(QMouseEvent *event) +{ + double pos = event->pos().x() / (double)width(); + setValue(pos * (maximum() - minimum()) + minimum()); + emit sliderReleased(); + return QSlider::mouseReleaseEvent(event); +} diff --git a/seekableslider.h b/seekableslider.h index 3ed4e50..92a5a44 100644 --- a/seekableslider.h +++ b/seekableslider.h @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2024 Gary Wang +// +// SPDX-License-Identifier: MIT + #pragma once #include @@ -8,13 +12,13 @@ class SeekableSlider : public QSlider Q_OBJECT public: explicit SeekableSlider(QWidget *parent = nullptr); + ~SeekableSlider() = default; signals: public slots: protected: - void mousePressEvent(QMouseEvent *event) override; - void mouseMoveEvent(QMouseEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; }; diff --git a/singleapplicationmanager.cpp b/singleapplicationmanager.cpp index 4be5fd2..b1a4324 100644 --- a/singleapplicationmanager.cpp +++ b/singleapplicationmanager.cpp @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2024 Gary Wang +// +// SPDX-License-Identifier: MIT + #include "singleapplicationmanager.h" #include diff --git a/singleapplicationmanager.h b/singleapplicationmanager.h index 3fdb17c..abb6a72 100644 --- a/singleapplicationmanager.h +++ b/singleapplicationmanager.h @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2024 Gary Wang +// +// SPDX-License-Identifier: MIT + #ifndef SINGLEAPPLICATIONMANAGER_H #define SINGLEAPPLICATIONMANAGER_H