├── .clang-format ├── .gitignore ├── .kde-ci.yml ├── CMakeLists.txt ├── LICENSES └── LGPL-3.0.txt ├── Messages.sh ├── README.md ├── cmake └── FindTagLib.cmake ├── logo.png ├── org.kde.vvave.appdata.xml ├── org.kde.vvave.desktop ├── po ├── ar │ └── vvave.po ├── ca │ └── vvave.po ├── ca@valencia │ └── vvave.po ├── cs │ └── vvave.po ├── de │ └── vvave.po ├── en_GB │ └── vvave.po ├── eo │ └── vvave.po ├── es │ └── vvave.po ├── eu │ └── vvave.po ├── fi │ └── vvave.po ├── fr │ └── vvave.po ├── gl │ └── vvave.po ├── he │ └── vvave.po ├── hu │ └── vvave.po ├── ia │ └── vvave.po ├── it │ └── vvave.po ├── ja │ └── vvave.po ├── ka │ └── vvave.po ├── ko │ └── vvave.po ├── lt │ └── vvave.po ├── lv │ └── vvave.po ├── nl │ └── vvave.po ├── pl │ └── vvave.po ├── pt │ └── vvave.po ├── pt_BR │ └── vvave.po ├── sa │ └── vvave.po ├── sk │ └── vvave.po ├── sl │ └── vvave.po ├── sv │ └── vvave.po ├── tr │ └── vvave.po ├── uk │ └── vvave.po ├── zh_CN │ └── vvave.po └── zh_TW │ └── vvave.po └── src ├── CMakeLists.txt ├── android_files ├── AndroidManifest.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── res │ ├── drawable-hdpi │ │ └── icon.png │ ├── drawable-ldpi │ │ └── icon.png │ ├── drawable-mdpi │ │ └── icon.png │ ├── values │ │ └── libs.xml │ └── xml │ │ └── filepaths.xml └── version.gradle.in ├── assets.qrc ├── assets ├── cover.png ├── media-album-track.svg ├── playing.gif ├── view-media-track.svg ├── vvave (3).png ├── vvave.notifyrc ├── vvave.png ├── vvave.svg ├── vvave192.png ├── vvave72.png └── vvave96.png ├── db ├── Queries.js ├── collectionDB.cpp ├── collectionDB.h └── script.sql ├── kde ├── mpris2 │ ├── mediaplayer2.cpp │ ├── mediaplayer2.h │ ├── mediaplayer2player.cpp │ ├── mediaplayer2player.h │ ├── mpris2.cpp │ └── mpris2.h ├── server.cpp └── server.h ├── macos_files └── vvave.icns ├── main.cpp ├── main.qml ├── models ├── albums │ ├── albumsmodel.cpp │ └── albumsmodel.h ├── cloud │ ├── cloud.cpp │ └── cloud.h ├── folders │ ├── foldersmodel.cpp │ └── foldersmodel.h ├── playlists │ ├── playlistsmodel.cpp │ └── playlistsmodel.h └── tracks │ ├── overviewmodel.cpp │ ├── overviewmodel.h │ ├── tracksmodel.cpp │ └── tracksmodel.h ├── pulpo ├── enums.h ├── htmlparser.cpp ├── htmlparser.h ├── pulpo.cpp ├── pulpo.h ├── service.cpp ├── service.h └── services │ ├── deezerService.cpp │ ├── deezerService.h │ ├── geniusService.cpp │ ├── geniusService.h │ ├── lastfmService.cpp │ ├── lastfmService.h │ ├── lyricwikiaService.cpp │ ├── lyricwikiaService.h │ ├── musicbrainzService.cpp │ ├── musicbrainzService.h │ ├── spotifyService.cpp │ └── spotifyService.h ├── services ├── local │ ├── artworkprovider.cpp │ ├── artworkprovider.h │ ├── metadataeditor.cpp │ ├── metadataeditor.h │ ├── player.cpp │ ├── player.h │ ├── playlist.cpp │ ├── playlist.h │ ├── powermanagementinterface.cpp │ ├── powermanagementinterface.h │ ├── taginfo.cpp │ ├── taginfo.h │ ├── trackinfo.cpp │ └── trackinfo.h └── web │ ├── NextCloud │ ├── nextmusic.cpp │ └── nextmusic.h │ ├── abstractmusicprovider.cpp │ └── abstractmusicprovider.h ├── utils ├── Player.js └── bae.h ├── vvave.cpp ├── vvave.h ├── widgets ├── AlbumsView.qml ├── BabeGrid │ └── BabeGrid.qml ├── BabeTable │ ├── BabeTable.qml │ ├── TableDelegate.qml │ └── TableMenu.qml ├── CloudView │ └── CloudView.qml ├── FloatingDisk.qml ├── FocusView.qml ├── FoldersView │ └── FoldersView.qml ├── InfoView │ ├── InfoView.qml │ └── LyricsView.qml ├── MainPlaylist │ └── MainPlaylist.qml ├── MetadataDialog.qml ├── MiniMode.qml ├── PlaylistsView │ ├── PlaylistsView.qml │ └── PlaylistsViewModel.qml ├── SelectionBar.qml ├── SettingsView │ ├── SettingsDialog.qml │ └── ShortcutsDialog.qml ├── SleepTimerDialog.qml ├── TracksGroup.qml └── TracksView.qml └── windows_files ├── README ├── config └── config.xml ├── packages └── org.maui.vvave │ └── meta │ ├── installscript.qs │ ├── license.txt │ └── package.xml └── vvave.ico /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | # SPDX-FileCopyrightText: 2019 Christoph Cullmann 3 | # SPDX-FileCopyrightText: 2019 Gernot Gebhard 4 | # 5 | # SPDX-License-Identifier: MIT 6 | 7 | # This file got automatically created by ECM, do not edit 8 | # See https://clang.llvm.org/docs/ClangFormatStyleOptions.html for the config options 9 | # and https://community.kde.org/Policies/Frameworks_Coding_Style#Clang-format_automatic_code_formatting 10 | # for clang-format tips & tricks 11 | --- 12 | Language: JavaScript 13 | DisableFormat: true 14 | --- 15 | Language: Json 16 | DisableFormat: false 17 | IndentWidth: 4 18 | --- 19 | 20 | # Style for C++ 21 | Language: Cpp 22 | 23 | # base is WebKit coding style: https://webkit.org/code-style-guidelines/ 24 | # below are only things set that diverge from this style! 25 | BasedOnStyle: WebKit 26 | 27 | # enforce C++11 (e.g. for std::vector> 28 | Standard: Cpp11 29 | 30 | # 4 spaces indent 31 | TabWidth: 4 32 | 33 | # 2 * 80 wide lines 34 | ColumnLimit: 160 35 | 36 | # sort includes inside line separated groups 37 | SortIncludes: true 38 | 39 | # break before braces on function, namespace and class definitions. 40 | BreakBeforeBraces: Linux 41 | 42 | # CrlInstruction *a; 43 | PointerAlignment: Right 44 | 45 | # horizontally aligns arguments after an open bracket. 46 | AlignAfterOpenBracket: Align 47 | 48 | # don't move all parameters to new line 49 | AllowAllParametersOfDeclarationOnNextLine: false 50 | 51 | # no single line functions 52 | AllowShortFunctionsOnASingleLine: None 53 | 54 | # no single line enums 55 | AllowShortEnumsOnASingleLine: false 56 | 57 | # always break before you encounter multi line strings 58 | AlwaysBreakBeforeMultilineStrings: true 59 | 60 | # don't move arguments to own lines if they are not all on the same 61 | BinPackArguments: false 62 | 63 | # don't move parameters to own lines if they are not all on the same 64 | BinPackParameters: false 65 | 66 | # In case we have an if statement with multiple lines the operator should be at the beginning of the line 67 | # but we do not want to break assignments 68 | BreakBeforeBinaryOperators: NonAssignment 69 | 70 | # format C++11 braced lists like function calls 71 | Cpp11BracedListStyle: true 72 | 73 | # do not put a space before C++11 braced lists 74 | SpaceBeforeCpp11BracedList: false 75 | 76 | # remove empty lines 77 | KeepEmptyLinesAtTheStartOfBlocks: false 78 | 79 | # no namespace indentation to keep indent level low 80 | NamespaceIndentation: None 81 | 82 | # we use template< without space. 83 | SpaceAfterTemplateKeyword: false 84 | 85 | # Always break after template declaration 86 | AlwaysBreakTemplateDeclarations: true 87 | 88 | # macros for which the opening brace stays attached. 89 | ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH, forever, Q_FOREVER, QBENCHMARK, QBENCHMARK_ONCE , wl_resource_for_each, wl_resource_for_each_safe ] 90 | 91 | # keep lambda formatting multi-line if not empty 92 | AllowShortLambdasOnASingleLine: Empty 93 | 94 | # We do not want clang-format to put all arguments on a new line 95 | AllowAllArgumentsOnNextLine: false 96 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | mauikit/ 2 | 3rdparty/kirigami/ 3 | 3rdparty/mauikit/ 4 | 3rdparty/breeze-icons/ 5 | 3rdparty/openssl/ 6 | 3rdparty/taglib 7 | 3rdparty/taglib/taglib.pri 8 | .directory 9 | 10 | *.kdev4 11 | *.*~ 12 | #~*.* 13 | #~.* 14 | *.DS_Store 15 | 16 | # C++ objects and libs 17 | *.slo 18 | *.lo 19 | *.o 20 | *.a 21 | *.la 22 | *.lai 23 | #*.so 24 | *.dll 25 | *.dylib 26 | 27 | # Qt-es 28 | object_script.*.Release 29 | object_script.*.Debug 30 | *_plugin_import.cpp 31 | /.qmake.cache 32 | /.qmake.stash 33 | *.pro.user 34 | *.pro.user.* 35 | *.qbs.user 36 | *.qbs.user.* 37 | *.moc 38 | moc_*.cpp 39 | moc_*.h 40 | qrc_*.cpp 41 | ui_*.h 42 | *.qmlc 43 | *.jsc 44 | Makefile* 45 | *build-* 46 | 47 | # Qt unit tests 48 | target_wrapper.* 49 | 50 | # QtCreator 51 | *.autosave 52 | 53 | # QtCreator Qml 54 | *.qmlproject.user 55 | *.qmlproject.user.* 56 | 57 | # QtCreator CMake 58 | CMakeLists.txt.user* 59 | 60 | # build folders and output 61 | build/ 62 | build.6/ 63 | vvave 64 | 65 | # LSP & IDE 66 | /.clang-format 67 | /compile_commands.json 68 | .clangd 69 | .cache 70 | .idea 71 | /cmake-build* 72 | -------------------------------------------------------------------------------- /.kde-ci.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: None 2 | # SPDX-License-Identifier: CC0-1.0 3 | 4 | Dependencies: 5 | - 'on': ['@all'] 6 | 'require': 7 | 'frameworks/extra-cmake-modules': '@latest' 8 | 'frameworks/kconfig': '@latest' 9 | 'frameworks/ki18n': '@latest' 10 | 'frameworks/kio': '@latest' 11 | 'frameworks/knotifications': '@latest' 12 | 'maui/mauikit': '@latest' 13 | 'maui/mauikit-accounts': '@latest' 14 | 'maui/mauikit-filebrowsing': '@latest' 15 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2018-2020 Camilo Higuita 2 | # Copyright 2018-2020 Nitrux Latinoamericana S.C. 3 | # 4 | # SPDX-License-Identifier: GPL-3.0-or-later 5 | 6 | cmake_minimum_required(VERSION 3.20) 7 | 8 | set(VVAVE_VERSION 4.0.2) 9 | add_compile_definitions(APP_COPYRIGHT_NOTICE="© 2019-2025 Maui Development Team") 10 | 11 | set(CMAKE_CXX_STANDARD 17) 12 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 13 | set(CMAKE_INCLUDE_CURRENT_DIR ON) 14 | 15 | set(CMAKE_AUTOUIC ON) 16 | set(CMAKE_AUTOMOC ON) 17 | set(CMAKE_AUTORCC ON) 18 | 19 | project(vvave VERSION ${VVAVE_VERSION} LANGUAGES CXX) 20 | 21 | set(QT_MAJOR_VERSION 6) 22 | 23 | set(REQUIRED_QT_VERSION 6.5.2) 24 | set(REQUIRED_KF_VERSION 5.240.0) 25 | 26 | find_package(ECM ${REQUIRED_KF_VERSION} REQUIRED NO_MODULE) 27 | set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH}) 28 | # set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake" ${ECM_MODULE_PATH}) 29 | 30 | if(UNIX AND NOT APPLE AND NOT ANDROID) 31 | include(KDEInstallDirs) 32 | include(KDECMakeSettings) 33 | endif() 34 | 35 | include(KDECompilerSettings NO_POLICY_SCOPE) 36 | include(KDEClangFormat) 37 | include(ECMSetupVersion) 38 | include(ECMSourceVersionControl) 39 | include(FeatureSummary) 40 | 41 | if(ANDROID) 42 | set(ANDROID_ABIS "arm64-v8a") 43 | endif() 44 | 45 | find_package(Qt6 ${REQUIRED_QT_VERSION} REQUIRED COMPONENTS Core Qml Quick QuickControls2 Network Sql Xml Multimedia) 46 | find_package(KF6 ${REQUIRED_KF_VERSION} REQUIRED COMPONENTS I18n CoreAddons) 47 | 48 | find_package(MauiKit4 REQUIRED COMPONENTS FileBrowsing) 49 | 50 | if(ANDROID) 51 | find_package(Taglib 1.4 REQUIRED) 52 | # target_link_libraries 53 | else() 54 | find_package(Taglib 1.4 REQUIRED) 55 | endif() 56 | 57 | if(UNIX AND NOT ANDROID) 58 | find_package(Qt6 ${REQUIRED_QT_VERSION} REQUIRED COMPONENTS DBus) 59 | endif() 60 | 61 | if(UNIX OR WIN32 OR APPLE AND NOT ANDROID) 62 | find_package(Qt6 ${REQUIRED_QT_VERSION} REQUIRED COMPONENTS Widgets) 63 | endif() 64 | 65 | if(${ECM_SOURCE_UNDER_VERSION_CONTROL}) 66 | execute_process( 67 | COMMAND git rev-parse --abbrev-ref HEAD 68 | WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} 69 | OUTPUT_VARIABLE GIT_BRANCH 70 | OUTPUT_STRIP_TRAILING_WHITESPACE) 71 | 72 | # Get the latest abbreviated commit hash of the working branch 73 | execute_process( 74 | COMMAND git log -1 --format=%h 75 | WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} 76 | OUTPUT_VARIABLE GIT_COMMIT_HASH 77 | OUTPUT_STRIP_TRAILING_WHITESPACE) 78 | 79 | add_definitions(-DGIT_COMMIT_HASH="${GIT_COMMIT_HASH}") 80 | add_definitions(-DGIT_BRANCH="${GIT_BRANCH}") 81 | 82 | else() 83 | add_definitions(-DGIT_COMMIT_HASH="${VVAVE_VERSION}") 84 | add_definitions(-DGIT_BRANCH="Stable") 85 | endif() 86 | 87 | ecm_setup_version(${VVAVE_VERSION} 88 | VARIABLE_PREFIX VVAVE 89 | VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/vvave_version.h") 90 | 91 | qt_policy(SET QTP0001 OLD) 92 | qt_policy(SET QTP0004 NEW) 93 | 94 | add_subdirectory(src) 95 | 96 | ki18n_install(po) 97 | 98 | if(UNIX AND NOT APPLE AND NOT ANDROID) 99 | install(FILES org.kde.vvave.desktop DESTINATION ${KDE_INSTALL_APPDIR}) 100 | install(FILES org.kde.vvave.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR}) 101 | endif() 102 | 103 | feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) 104 | 105 | file(GLOB_RECURSE ALL_CLANG_FORMAT_SOURCE_FILES *.cpp *.h) 106 | kde_clang_format(${ALL_CLANG_FORMAT_SOURCE_FILES}) 107 | 108 | qt_finalize_project() 109 | 110 | -------------------------------------------------------------------------------- /Messages.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Copyright 2018-2020 Camilo Higuita 4 | # Copyright 2018-2020 Nitrux Latinoamericana S.C. 5 | # 6 | # SPDX-License-Identifier: GPL-3.0-or-later 7 | 8 | 9 | $XGETTEXT $(find src/ -name \*.cpp -o -name \*.h -o -name \*.qml) -o $podir/vvave.pot 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vvave 2 | 3 | Download on Flathub 4 | 5 | ## Building 6 | 7 | ### Build for Android 8 | Use `qmake`: 9 | 10 | ```bash 11 | # Clone the code 12 | git clone https://invent.kde.org/kde/vvave.git 13 | cd vvave 14 | # Create build dir 15 | mkdir build && cd build 16 | # Build 17 | qmake -o Makefile ../vvave.pro 18 | make 19 | ``` 20 | 21 | ### Build for Desktop 22 | Use `cmake`: 23 | ```bash 24 | # Clone the code 25 | git clone https://invent.kde.org/kde/vvave.git 26 | cd vvave 27 | # Create build dir 28 | mkdir build && cd build 29 | # Build 30 | cmake .. 31 | make 32 | sudo make install 33 | ``` 34 | 35 | ### Dependencies 36 | 37 | If you've built vvave on some distro, please contribute here! 38 | 39 | #### Ubuntu 40 | 41 | ``` 42 | sudo apt install kirigami2-dev libkf5syntaxhighlighting-dev extra-cmake-modules libtag1-dev libkf5notifications-dev libqt5websockets5-dev libqt5webview5-dev qtdeclarative5-dev qtmultimedia5-dev qtwebengine5-dev qtbase5-dev 43 | ``` 44 | 45 | For other distros, the `buildInputs` part of the next section is a good clue for what 46 | you need. 47 | 48 | ### Using a nix shell 49 | 50 | If you use `nix` you don't have to get your environment dirty, here are all the 51 | dependencies and environment variables you need to load: 52 | 53 | ```nix 54 | with import {}; 55 | 56 | let qtx = qt5; in 57 | stdenv.mkDerivation { 58 | name = "vvave"; 59 | 60 | buildInputs = [ 61 | appstream 62 | taglib 63 | gettext 64 | ] ++ (with libsForQt5; [ 65 | ki18n 66 | kconfig 67 | knotifications 68 | kservice 69 | kio 70 | kirigami2 71 | ]) ++ (with qtx; [ 72 | qtbase 73 | qtwebsockets 74 | qtquickcontrols 75 | qtquickcontrols2 76 | qtmultimedia 77 | qtwebengine 78 | qtgraphicaleffects 79 | qtdeclarative 80 | ]) ++ (with gst_all_1; [ 81 | gst-plugins-base 82 | gst-plugins-good 83 | gst-plugins-bad 84 | gst-plugins-ugly 85 | gst-libav 86 | ]); 87 | 88 | shellHook = with qtx; '' 89 | export QT_QPA_PLATFORM_PLUGIN_PATH="${qtbase}/${qtbase.qtPluginPrefix}/platforms" 90 | export QT_PLUGIN_PATH="$QT_PLUGIN_PATH:${qtbase.bin}/${qtbase.qtPluginPrefix}" 91 | export QML2_IMPORT_PATH="$QML2_IMPORT_PATH:${qtquickcontrols2.bin}/${qtbase.qtQmlPrefix}" 92 | export QML2_IMPORT_PATH="$QML2_IMPORT_PATH:${qtquickcontrols}/${qtbase.qtQmlPrefix}" 93 | export QML2_IMPORT_PATH="$QML2_IMPORT_PATH:${qtgraphicaleffects}/${qtbase.qtQmlPrefix}" 94 | export QML2_IMPORT_PATH="$QML2_IMPORT_PATH:${qtdeclarative.bin}/${qtbase.qtQmlPrefix}" 95 | export QML2_IMPORT_PATH="$QML2_IMPORT_PATH:${libsForQt5.kirigami2}/${qtbase.qtQmlPrefix}" 96 | ''; 97 | } 98 | ``` 99 | 100 | ## Troubleshooting 101 | 102 | ### QML complains module X is not installed 103 | 104 | Check that all of the following Qt Components are installed: 105 | 106 | ``` 107 | qtbase qtquickcontrols2 qtquickcontrols qtgraphicaleffects qtdeclarative kirigami2 108 | ``` 109 | 110 | Next check that module `X` can be found in `$QML2_IMPORT_PATH`. 111 | 112 | ### VVAVE is built and running but no sound comes out! 113 | 114 | Check that you have all the correct gstreamer plugins installed: 115 | 116 | ``` 117 | gst-plugins-base gst-plugins-good gst-plugins-bad gst-plugins-ugly gst-libav 118 | ``` 119 | -------------------------------------------------------------------------------- /cmake/FindTagLib.cmake: -------------------------------------------------------------------------------- 1 | # - Try to find the Taglib library 2 | # Once done this will define 3 | # 4 | # TAGLIB_FOUND - system has the taglib library 5 | # TAGLIB_CFLAGS - the taglib cflags 6 | # TAGLIB_LIBRARIES - The libraries needed to use taglib 7 | 8 | # Copyright (c) 2006, Laurent Montel, 9 | # 10 | # Redistribution and use is allowed according to the terms of the BSD license. 11 | # For details see the accompanying COPYING-CMAKE-SCRIPTS file. 12 | 13 | if(NOT TAGLIB_MIN_VERSION) 14 | set(TAGLIB_MIN_VERSION "1.4") 15 | endif(NOT TAGLIB_MIN_VERSION) 16 | 17 | if(NOT WIN32) 18 | find_program(TAGLIBCONFIG_EXECUTABLE NAMES taglib-config PATHS 19 | ${BIN_INSTALL_DIR} 20 | ) 21 | endif(NOT WIN32) 22 | 23 | #reset vars 24 | set(TAGLIB_LIBRARIES) 25 | set(TAGLIB_CFLAGS) 26 | 27 | # if taglib-config has been found 28 | if(TAGLIBCONFIG_EXECUTABLE) 29 | 30 | exec_program(${TAGLIBCONFIG_EXECUTABLE} ARGS --version RETURN_VALUE _return_VALUE OUTPUT_VARIABLE TAGLIB_VERSION) 31 | 32 | if(TAGLIB_VERSION VERSION_LESS "${TAGLIB_MIN_VERSION}") 33 | message(STATUS "TagLib version not found: version searched :${TAGLIB_MIN_VERSION}, found ${TAGLIB_VERSION}") 34 | set(TAGLIB_FOUND FALSE) 35 | else(TAGLIB_VERSION VERSION_LESS "${TAGLIB_MIN_VERSION}") 36 | 37 | exec_program(${TAGLIBCONFIG_EXECUTABLE} ARGS --libs RETURN_VALUE _return_VALUE OUTPUT_VARIABLE TAGLIB_LIBRARIES) 38 | 39 | exec_program(${TAGLIBCONFIG_EXECUTABLE} ARGS --cflags RETURN_VALUE _return_VALUE OUTPUT_VARIABLE TAGLIB_CFLAGS) 40 | 41 | if(TAGLIB_LIBRARIES AND TAGLIB_CFLAGS) 42 | set(TAGLIB_FOUND TRUE) 43 | message(STATUS "Found taglib: ${TAGLIB_LIBRARIES}") 44 | endif(TAGLIB_LIBRARIES AND TAGLIB_CFLAGS) 45 | string(REGEX REPLACE " *-I" ";" TAGLIB_INCLUDES "${TAGLIB_CFLAGS}") 46 | # string(SUBSTRING ${TAGLIB_INCLUDES} 0 -1 TAGLIB_INCLUDES) 47 | endif(TAGLIB_VERSION VERSION_LESS "${TAGLIB_MIN_VERSION}") 48 | mark_as_advanced(TAGLIB_CFLAGS TAGLIB_LIBRARIES TAGLIB_INCLUDES) 49 | 50 | else(TAGLIBCONFIG_EXECUTABLE) 51 | 52 | include(FindLibraryWithDebug) 53 | include(FindPackageHandleStandardArgs) 54 | 55 | find_path(TAGLIB_INCLUDES 56 | NAMES 57 | tag.h 58 | PATH_SUFFIXES taglib 59 | PATHS 60 | ${KDE4_INCLUDE_DIR} 61 | ${INCLUDE_INSTALL_DIR} 62 | ) 63 | list(APPEND TAGLIB_INCLUDES "${TAGLIB_INCLUDES}/..") 64 | 65 | find_library_with_debug(TAGLIB_LIBRARIES 66 | WIN32_DEBUG_POSTFIX d 67 | NAMES tag 68 | PATHS 69 | ${KDE4_LIB_DIR} 70 | ${LIB_INSTALL_DIR} 71 | ) 72 | 73 | find_package_handle_standard_args(Taglib DEFAULT_MSG 74 | TAGLIB_INCLUDES TAGLIB_LIBRARIES) 75 | endif(TAGLIBCONFIG_EXECUTABLE) 76 | 77 | 78 | if(TAGLIB_FOUND) 79 | if(NOT Taglib_FIND_QUIETLY AND TAGLIBCONFIG_EXECUTABLE) 80 | message(STATUS "Taglib found: ${TAGLIB_LIBRARIES}") 81 | endif(NOT Taglib_FIND_QUIETLY AND TAGLIBCONFIG_EXECUTABLE) 82 | else(TAGLIB_FOUND) 83 | if(Taglib_FIND_REQUIRED) 84 | message(FATAL_ERROR "Could not find Taglib") 85 | endif(Taglib_FIND_REQUIRED) 86 | endif(TAGLIB_FOUND) 87 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/vvave/ff68d3bd59e5594c2d0627bdd5e5416d7b614530/logo.png -------------------------------------------------------------------------------- /org.kde.vvave.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Name=Vvave 3 | Name[ar]=فاف 4 | Name[ca]=Vvave 5 | Name[ca@valencia]=Vvave 6 | Name[cs]=Vvave 7 | Name[de]=Vvave 8 | Name[el]=Vvave 9 | Name[en_GB]=Vvave 10 | Name[eo]=Vvave 11 | Name[es]=Vvave 12 | Name[eu]=Vvave 13 | Name[fi]=Vvave 14 | Name[fr]=Vvave 15 | Name[gl]=Vvave 16 | Name[he]=Vvave 17 | Name[hi]=वीवेव 18 | Name[hu]=Vvave 19 | Name[ia]=Vvave 20 | Name[id]=Vvave 21 | Name[it]=Vvave 22 | Name[ka]=Vvave 23 | Name[ko]=Vvave 24 | Name[lt]=Vvave 25 | Name[lv]=Vvave 26 | Name[nl]=Vvave 27 | Name[nn]=Vvave 28 | Name[pa]=ਵੇਵੇਵ 29 | Name[pl]=Vvave 30 | Name[pt]=Vvave 31 | Name[pt_BR]=Vvave 32 | Name[ro]=Vvave 33 | Name[sa]=व्वावे 34 | Name[sk]=Vvave 35 | Name[sl]=Vvave 36 | Name[sv]=Vvave 37 | Name[tr]=Vvave 38 | Name[uk]=Vvave 39 | Name[x-test]=xxVvavexx 40 | Name[zh_CN]=Vvave 41 | Name[zh_TW]=Vvave 42 | Comment=Play your music collection 43 | Comment[ar]=شغّل مجموعتك الموسيقية 44 | Comment[ca]=Reprodueix la vostra col·lecció de música 45 | Comment[ca@valencia]=Reproduïx la vostra col·lecció de música 46 | Comment[cs]=Přehrávejte svou hudební sbírku 47 | Comment[da]=Afspil din musiksamling 48 | Comment[de]=Wiedergabe Ihrer Musik-Sammlung 49 | Comment[el]=Αναπαράγετε τη μουσική συλλογή σας 50 | Comment[en_GB]=Play your music collection 51 | Comment[eo]=Ludu vian muzikaron 52 | Comment[es]=Reproducir su colección de música 53 | Comment[et]=Muusikakogu esitamine 54 | Comment[eu]=Jo zure musika bilduma 55 | Comment[fi]=Soita musiikkikokoelmaasi 56 | Comment[fr]=Jouez votre collection musicale 57 | Comment[gl]=Reproduza a súa colección de música. 58 | Comment[he]=ניגון אוסף המוזיקה שלך 59 | Comment[hi]=अपना संगीत संग्रह बजाएँ 60 | Comment[hu]=Játssza le zenei gyűjteményét 61 | Comment[ia]=Reproduce tu collection de musica 62 | Comment[id]=Mainkan koleksi musikmu 63 | Comment[it]=Riproduce la tua collezione musicale 64 | Comment[ka]=მუსიკის თქვენი კოლექციის დაკვრა 65 | Comment[ko]=내 음악 모음집 재생 66 | Comment[lt]=Atkurti muzikinę kolekciją 67 | Comment[lv]=Atskaņojiet savu mūzikas kolekciju 68 | Comment[nl]=Uw muziekcollectie afspelen 69 | Comment[nn]=Spel av musikksamlinga di 70 | Comment[pa]=ਆਪਣੇ ਸੰਗੀਤ ਭੰਡਾਰ ਚਲਾਓ 71 | Comment[pl]=Odtwarza swój zbiór muzyki 72 | Comment[pt]=Tocar a sua colecção de música 73 | Comment[pt_BR]=Reproduza sua coleção de músicas 74 | Comment[ro]=Redați-vă colecția muzicală 75 | Comment[sa]=स्वस्य सङ्गीतसङ्ग्रहं वादयन्तु 76 | Comment[sk]=Prehrať vašu hudobnú kolekciu 77 | Comment[sl]=Predvajajte svojo glasbeno zbirko 78 | Comment[sv]=Spela din musiksamling 79 | Comment[tr]=Müzik koleksiyonunuzu çalın 80 | Comment[uk]=Відтворення вашої музичної збірки 81 | Comment[x-test]=xxPlay your music collectionxx 82 | Comment[zh_CN]=播放您收藏的音乐 83 | Comment[zh_TW]=播放您的音樂收藏 84 | TryExec=vvave 85 | Exec=vvave %U 86 | Terminal=false 87 | Type=Application 88 | Categories=Qt;Audio;Music;Player;AudioVideo; 89 | StartupNotify=true 90 | Icon=vvave 91 | MimeType=application/ogg;application/x-ogg;application/sdp;application/smil;application/x-smil;application/streamingmedia;application/x-streamingmedia;application/vnd.rn-realmedia;application/vnd.rn-realmedia-vbr;audio/aac;audio/x-aac;audio/m4a;audio/x-m4a;audio/mp1;audio/x-mp1;audio/mp2;audio/x-mp2;audio/mp3;audio/x-mp3;audio/mpeg;audio/x-mpeg;audio/mpegurl;audio/x-mpegurl;audio/mpg;audio/x-mpg;audio/rn-mpeg;audio/ogg;audio/scpls;audio/x-scpls;audio/vnd.rn-realaudio;audio/wav;audio/x-pn-windows-pcm;audio/x-realaudio;audio/x-pn-realaudio;audio/x-ms-wma;audio/x-pls;audio/x-wav;audio/x-flac;audio/x-vorbis+ogg;audio/x-shorten;audio/x-ape;audio/x-wavpack;audio/x-tta;audio/AMR;audio/ac3;audio/flac;audio/mp4; 92 | # Generic name with translations 93 | GenericName=Audio Player 94 | GenericName[ar]=مشغل صوتي 95 | GenericName[ca]=Reproductor d'àudio 96 | GenericName[ca@valencia]=Reproductor d'àudio 97 | GenericName[cs]=Zvukový přehrávač 98 | GenericName[da]=Lydafspiller 99 | GenericName[de]=Audio-Wiedergabe 100 | GenericName[el]=Αναπαραγωγέας ήχου 101 | GenericName[en_GB]=Audio Player 102 | GenericName[eo]=Aŭdio-Ludilo 103 | GenericName[es]=Reproductor de sonido 104 | GenericName[et]=Helimängija 105 | GenericName[eu]=Audio jotzailea 106 | GenericName[fi]=Musiikkisoitin 107 | GenericName[fr]=Lecteur audio 108 | GenericName[gl]=Reprodutor de son 109 | GenericName[he]=נגן מוזיקה 110 | GenericName[hi]=ध्वनि वादक 111 | GenericName[hu]=Hanglejátszó 112 | GenericName[ia]=Reproductor de audio 113 | GenericName[id]=Pemain Audio 114 | GenericName[it]=Lettore audio 115 | GenericName[ka]=მუსიკის დამკვრელი 116 | GenericName[ko]=오디오 재생기 117 | GenericName[lt]=Garso grotuvas 118 | GenericName[lv]=Mūzikas atskaņotājs 119 | GenericName[nl]=Audiospeler 120 | GenericName[nn]=Musikkspelar 121 | GenericName[pa]=ਆਡੀਓ ਪਲੇਅਰ 122 | GenericName[pl]=Odtwarzacz muzyki 123 | GenericName[pt]=Leitor de Áudio 124 | GenericName[pt_BR]=Reprodutor de áudio 125 | GenericName[ro]=Redare audio 126 | GenericName[sa]=ऑडियो प्लेयर 127 | GenericName[sk]=Audio prehrávač 128 | GenericName[sl]=Predvajalnik zvoka 129 | GenericName[sv]=Ljudspelare 130 | GenericName[tr]=Ses Çalar 131 | GenericName[uk]=Аудіопрогравач 132 | GenericName[x-test]=xxAudio Playerxx 133 | GenericName[zh_CN]=音频播放器 134 | GenericName[zh_TW]=音樂播放器 135 | SingleMainWindow=true 136 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright 2018-2020 Camilo Higuita 2 | # Copyright 2018-2020 Nitrux Latinoamericana S.C. 3 | # 4 | # SPDX-License-Identifier: GPL-3.0-or-later 5 | 6 | remove_definitions(-DQT_NO_CAST_FROM_ASCII -DQT_STRICT_ITERATORS -DQT_NO_CAST_FROM_BYTEARRAY) 7 | 8 | include_directories( 9 | ${CMAKE_CURRENT_SOURCE_DIR}/services/web) 10 | 11 | set(vvave_SRCS 12 | main.cpp 13 | vvave.cpp 14 | # pulpo/services/geniusService.cpp 15 | # pulpo/services/deezerService.cpp 16 | pulpo/services/lastfmService.cpp 17 | # pulpo/services/lyricwikiaService.cpp 18 | pulpo/services/spotifyService.cpp 19 | # pulpo/services/musicbrainzService.cpp 20 | pulpo/pulpo.cpp 21 | pulpo/service.cpp 22 | services/local/taginfo.cpp 23 | services/local/artworkprovider.cpp 24 | services/local/player.cpp 25 | services/local/playlist.cpp 26 | services/local/trackinfo.cpp 27 | services/local/metadataeditor.cpp 28 | services/local/powermanagementinterface.cpp 29 | # services/web/NextCloud/nextmusic.cpp 30 | # services/web/abstractmusicprovider.cpp 31 | db/collectionDB.cpp 32 | models/tracks/tracksmodel.cpp 33 | models/playlists/playlistsmodel.cpp 34 | models/albums/albumsmodel.cpp 35 | models/folders/foldersmodel.cpp 36 | # models/cloud/cloud.cpp 37 | kde/mpris2/mpris2.cpp 38 | kde/server.cpp) 39 | 40 | qt_add_resources(vvave_QML_QRC 41 | assets.qrc) 42 | 43 | if(UNIX AND NOT ANDROID) 44 | qt_generate_dbus_interface(${CMAKE_CURRENT_SOURCE_DIR}/kde/server.h org.kde.Vvave.xml) 45 | qt_add_dbus_adaptor(vvave_dbus_SRCS ${CMAKE_CURRENT_BINARY_DIR}/org.kde.Vvave.xml kde/server.h Server) 46 | qt_add_dbus_interface(vvave_dbus_SRCS ${CMAKE_CURRENT_BINARY_DIR}/org.kde.Vvave.xml vvaveinterface) 47 | endif() 48 | 49 | if(ANDROID) 50 | 51 | qt_add_executable(${PROJECT_NAME} 52 | MANUAL_FINALIZATION 53 | ${vvave_SRCS} 54 | ${vvave_QML_QRC}) 55 | 56 | set_property(TARGET ${PROJECT_NAME} APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/android_files) 57 | configure_file(${CMAKE_CURRENT_SOURCE_DIR}/android_files/version.gradle.in ${CMAKE_BINARY_DIR}/src/version.gradle) 58 | 59 | include(/home/camilo/Android/Sdk/android_openssl/android_openssl.cmake) 60 | add_android_openssl_libraries(${PROJECT_NAME}) 61 | else() 62 | qt_add_executable(${PROJECT_NAME} 63 | ${vvave_SRCS} 64 | ${vvave_dbus_SRCS} 65 | ${vvave_QML_QRC}) 66 | endif() 67 | 68 | set_source_files_properties(utils/Player.js PROPERTIES QT_QML_SKIP_QMLDIR_ENTRY TRUE) 69 | set_source_files_properties(db/Queries.js PROPERTIES QT_QML_SKIP_QMLDIR_ENTRY TRUE) 70 | 71 | qt_add_qml_module(${PROJECT_NAME} URI app.maui.vvave 72 | NO_PLUGIN 73 | QML_FILES 74 | main.qml 75 | widgets/TracksView.qml 76 | widgets/AlbumsView.qml 77 | widgets/MainPlaylist/MainPlaylist.qml 78 | widgets/PlaylistsView/PlaylistsView.qml 79 | widgets/InfoView/InfoView.qml 80 | widgets/InfoView/LyricsView.qml 81 | widgets/PlaylistsView/PlaylistsViewModel.qml 82 | widgets/FloatingDisk.qml 83 | widgets/CloudView/CloudView.qml 84 | widgets/SelectionBar.qml 85 | widgets/FocusView.qml 86 | widgets/SettingsView/SettingsDialog.qml 87 | widgets/SettingsView/ShortcutsDialog.qml 88 | widgets/FoldersView/FoldersView.qml 89 | widgets/MetadataDialog.qml 90 | widgets/BabeTable/TableDelegate.qml 91 | widgets/BabeTable/TableMenu.qml 92 | widgets/BabeTable/BabeTable.qml 93 | widgets/BabeGrid/BabeGrid.qml 94 | widgets/MiniMode.qml 95 | widgets/TracksGroup.qml 96 | widgets/SleepTimerDialog.qml 97 | 98 | utils/Player.js 99 | db/Queries.js) 100 | 101 | if (ANDROID) 102 | # find_package(OpenSSL REQUIRED) 103 | 104 | # target_link_libraries(${PROJECT_NAME} 105 | # PRIVATE 106 | # OpenSSL::SSL) 107 | 108 | kde_source_files_enable_exceptions(${PROJECT_NAME}) 109 | 110 | # target_link_libraries(${PROJECT_NAME} PRIVATE -L/home/camilo/Qt/6.9.0/android_arm64_v8a/lib/libtag_arm64-v8a.so) 111 | # target_link_libraries(${PROJECT_NAME} PRIVATE -L/home/camilo/Qt/6.9.0/android_arm64_v8a/lib/libtag_c_arm64-v8a.so) 112 | # target_include_directories(${PROJECT_NAME} PUBLIC "/home/camilo/Qt/6.9.0/android_arm64_v8a/include/taglib") 113 | 114 | set(Taglib_LIBRARIES 115 | Taglib::Taglib) 116 | 117 | elseif(UNIX AND NOT WIN32 AND NOT APPLE) 118 | target_sources(${PROJECT_NAME} 119 | PRIVATE 120 | kde/mpris2/mediaplayer2.cpp 121 | kde/mpris2/mediaplayer2player.cpp) 122 | 123 | target_link_libraries(${PROJECT_NAME} 124 | PRIVATE 125 | Qt6::DBus) 126 | 127 | set(Taglib_LIBRARIES 128 | Taglib::Taglib) 129 | endif() 130 | 131 | if(UNIX OR WIN32 OR APPLE AND NOT ANDROID) 132 | target_link_libraries(${PROJECT_NAME} 133 | PRIVATE 134 | Qt6::Widgets) 135 | endif() 136 | 137 | target_compile_definitions(${PROJECT_NAME} 138 | PRIVATE $<$,$>:QT_QML_DEBUG>) 139 | 140 | target_include_directories(${PROJECT_NAME} PRIVATE ${Taglib_INCLUDE_DIRS}) 141 | 142 | target_link_libraries(${PROJECT_NAME} 143 | PRIVATE 144 | MauiKit4 145 | MauiKit4::FileBrowsing 146 | Qt6::Core 147 | Qt6::Network 148 | Qt6::Sql 149 | Qt6::Qml 150 | Qt6::Xml 151 | Qt6::Multimedia 152 | Qt6::QuickControls2 153 | KF6::CoreAddons 154 | KF6::I18n 155 | ${Taglib_LIBRARIES}) 156 | 157 | if(UNIX AND NOT APPLE AND NOT ANDROID) 158 | install(TARGETS ${PROJECT_NAME} ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) 159 | install(FILES assets/vvave.svg DESTINATION ${KDE_INSTALL_ICONDIR}/hicolor/scalable/apps) 160 | endif() 161 | 162 | qt_finalize_target(${PROJECT_NAME}) 163 | -------------------------------------------------------------------------------- /src/android_files/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/android_files/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | jcenter() 5 | } 6 | 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:7.4.1' 9 | } 10 | } 11 | 12 | repositories { 13 | google() 14 | jcenter() 15 | } 16 | 17 | apply plugin: 'com.android.application' 18 | apply from: '../version.gradle' 19 | def timestamp = (int)(new Date().getTime()/1000) 20 | 21 | dependencies { 22 | implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar']) 23 | implementation 'androidx.core:core:1.10.1' 24 | } 25 | 26 | android { 27 | /******************************************************* 28 | * The following variables: 29 | * - androidBuildToolsVersion, 30 | * - androidCompileSdkVersion 31 | * - qtAndroidDir - holds the path to qt android files 32 | * needed to build any Qt application 33 | * on Android. 34 | * 35 | * are defined in gradle.properties file. This file is 36 | * updated by QtCreator and androiddeployqt tools. 37 | * Changing them manually might break the compilation! 38 | *******************************************************/ 39 | 40 | compileSdkVersion androidCompileSdkVersion 41 | buildToolsVersion androidBuildToolsVersion 42 | ndkVersion androidNdkVersion 43 | 44 | // Extract native libraries from the APK 45 | packagingOptions.jniLibs.useLegacyPackaging true 46 | 47 | sourceSets { 48 | main { 49 | manifest.srcFile 'AndroidManifest.xml' 50 | java.srcDirs = [qtAndroidDir + '/src', 'src', 'java'] 51 | aidl.srcDirs = [qtAndroidDir + '/src', 'src', 'aidl'] 52 | res.srcDirs = [qtAndroidDir + '/res', 'res'] 53 | resources.srcDirs = ['resources'] 54 | renderscript.srcDirs = ['src'] 55 | assets.srcDirs = ['assets'] 56 | jniLibs.srcDirs = ['libs'] 57 | } 58 | } 59 | 60 | tasks.withType(JavaCompile) { 61 | options.incremental = true 62 | } 63 | 64 | compileOptions { 65 | sourceCompatibility JavaVersion.VERSION_1_8 66 | targetCompatibility JavaVersion.VERSION_1_8 67 | } 68 | 69 | lintOptions { 70 | abortOnError false 71 | } 72 | 73 | // Do not compress Qt binary resources file 74 | aaptOptions { 75 | noCompress 'rcc' 76 | } 77 | 78 | defaultConfig { 79 | minSdkVersion qtMinSdkVersion 80 | targetSdkVersion qtTargetSdkVersion 81 | applicationId "org.maui.vvave" 82 | namespace "org.maui.vvave" 83 | versionCode timestamp 84 | versionName projectVersionFull 85 | manifestPlaceholders = [versionName: projectVersionFull, versionCode: timestamp] 86 | 87 | // resConfig "en" 88 | // minSdkVersion qtMinSdkVersion 89 | // targetSdkVersion qtTargetSdkVersion 90 | // ndk.abiFilters = qtTargetAbiList.split(",") 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/android_files/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # For more details on how to configure your build environment visit 3 | # http://www.gradle.org/docs/current/userguide/build_environment.html 4 | # Specifies the JVM arguments used for the daemon process. 5 | # The setting is particularly useful for tweaking memory settings. 6 | org.gradle.jvmargs=-Xmx2500m -XX:MaxMetaspaceSize=768m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 7 | 8 | # Enable building projects in parallel 9 | org.gradle.parallel=true 10 | 11 | # Gradle caching allows reusing the build artifacts from a previous 12 | # build with the same inputs. However, over time, the cache size will 13 | # grow. Uncomment the following line to enable it. 14 | #org.gradle.caching=true 15 | #org.gradle.configuration-cache=true 16 | 17 | # Allow AndroidX usage 18 | android.useAndroidX=true 19 | -------------------------------------------------------------------------------- /src/android_files/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/vvave/ff68d3bd59e5594c2d0627bdd5e5416d7b614530/src/android_files/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /src/android_files/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /src/android_files/res/drawable-hdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/vvave/ff68d3bd59e5594c2d0627bdd5e5416d7b614530/src/android_files/res/drawable-hdpi/icon.png -------------------------------------------------------------------------------- /src/android_files/res/drawable-ldpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/vvave/ff68d3bd59e5594c2d0627bdd5e5416d7b614530/src/android_files/res/drawable-ldpi/icon.png -------------------------------------------------------------------------------- /src/android_files/res/drawable-mdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/vvave/ff68d3bd59e5594c2d0627bdd5e5416d7b614530/src/android_files/res/drawable-mdpi/icon.png -------------------------------------------------------------------------------- /src/android_files/res/values/libs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/android_files/res/xml/filepaths.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 9 | 12 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /src/android_files/version.gradle.in: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2021 Volker Krause 2 | // SPDX-License-Identifier: BSD-3-Clause 3 | 4 | ext { 5 | projectVersionFull = "@PROJECT_VERSION@" 6 | } 7 | -------------------------------------------------------------------------------- /src/assets.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | assets/cover.png 4 | utils/Player.js 5 | db/Queries.js 6 | assets/vvave.notifyrc 7 | assets/vvave.png 8 | assets/vvave.svg 9 | assets/view-media-track.svg 10 | assets/media-album-track.svg 11 | assets/playing.gif 12 | 13 | 14 | db/script.sql 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/assets/cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/vvave/ff68d3bd59e5594c2d0627bdd5e5416d7b614530/src/assets/cover.png -------------------------------------------------------------------------------- /src/assets/media-album-track.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 28 | 29 | 52 | 56 | 63 | 70 | 77 | 84 | 85 | 87 | 88 | 90 | image/svg+xml 91 | 93 | 94 | 95 | 96 | 97 | 102 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /src/assets/playing.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/vvave/ff68d3bd59e5594c2d0627bdd5e5416d7b614530/src/assets/playing.gif -------------------------------------------------------------------------------- /src/assets/view-media-track.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 28 | 29 | 52 | 56 | 63 | 70 | 77 | 84 | 85 | 87 | 88 | 90 | image/svg+xml 91 | 93 | 94 | 95 | 96 | 97 | 102 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /src/assets/vvave (3).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/vvave/ff68d3bd59e5594c2d0627bdd5e5416d7b614530/src/assets/vvave (3).png -------------------------------------------------------------------------------- /src/assets/vvave.notifyrc: -------------------------------------------------------------------------------- 1 | [Global] 2 | IconName=preferences-system-bluetooth 3 | Comment=Tiny Music Player 4 | Comment[ar]=مشغّل صوتي صغير 5 | Comment[ca]=Reproductor de música petit 6 | Comment[ca@valencia]=Reproductor de música xicotet 7 | Comment[cs]=Drobný přehrávač hudby 8 | Comment[da]=Lille musikafspiller 9 | Comment[de]=Einfache Audio-Wiedergabe 10 | Comment[el]=Μικροσκοπικό μουσικό κουτί 11 | Comment[en_GB]=Tiny Music Player 12 | Comment[eo]=Eta Muzikludilo 13 | Comment[es]=Pequeño reproductor de música 14 | Comment[et]=Tilluke muusikamängija 15 | Comment[eu]=Musika jotzaile ñimiñoa 16 | Comment[fi]=Pikkuruinen musiikkisoitin 17 | Comment[fr]=Lecteur de musique ultra petit 18 | Comment[gl]=Reprodutor de música anano. 19 | Comment[he]=נגן מוזיקה פצפון 20 | Comment[hi]=सूक्ष्म संगीत वादक 21 | Comment[hu]=Zenelejátszó 22 | Comment[ia]=Parve reproductor de musica 23 | Comment[id]=Pemain Musik Mungil 24 | Comment[it]=Piccolo lettore musicale 25 | Comment[ka]=მუსიკის ნამცეცა დამკვრელი 26 | Comment[ko]=작은 음악 재생기 27 | Comment[lt]=Mažas muzikos grotuvas 28 | Comment[lv]=Vienkāršs mūzikas atskaņotājs 29 | Comment[nl]=Kleine muziekspeler 30 | Comment[nn]=Liten musikkspelar 31 | Comment[pa]=ਨਿੰਮਾ ਜਿਹਾ ਸੰਗੀਤ ਪਲੇਅਰ 32 | Comment[pl]=Odtwarzacz muzyki 33 | Comment[pt]=Pequeno Leitor Multimédia 34 | Comment[pt_BR]=Pequeno reprodutor de música 35 | Comment[ro]=Program minuscul de redare audio 36 | Comment[sa]=लघु संगीत वादक 37 | Comment[sk]=Malý hudobný prehrávač 38 | Comment[sl]=Droben predvajalnik glasbe 39 | Comment[sv]=Mycket liten musikspelare 40 | Comment[tr]=Ufak ses çalar 41 | Comment[uk]=Маленький музичний програвач 42 | Comment[x-test]=xxTiny Music Playerxx 43 | Comment[zh_CN]=简易音乐播放器 44 | Comment[zh_TW]=輕巧的音樂播放器 45 | Name=vvave 46 | Name[ar]=فاف 47 | Name[ca]=vvave 48 | Name[ca@valencia]=vvave 49 | Name[cs]=vvave 50 | Name[da]=vvave 51 | Name[de]=vvave 52 | Name[el]=vvave 53 | Name[en_GB]=vvave 54 | Name[eo]=vvave 55 | Name[es]=vvave 56 | Name[et]=vvave 57 | Name[eu]=vvave 58 | Name[fi]=vvave 59 | Name[fr]=Vvave 60 | Name[gl]=vvave 61 | Name[he]=vvave 62 | Name[hi]=वीवेव 63 | Name[hu]=vvave 64 | Name[ia]=vvave 65 | Name[id]=vvave 66 | Name[it]=vvave 67 | Name[ka]=vvave 68 | Name[ko]=vvave 69 | Name[lt]=vvave 70 | Name[lv]=vvave 71 | Name[nl]=vvave 72 | Name[nn]=vvave 73 | Name[pa]=ਵੇਵੇਵ 74 | Name[pl]=vvave 75 | Name[pt]=vvave 76 | Name[pt_BR]=vvave 77 | Name[ro]=vvave 78 | Name[ru]=vvave 79 | Name[sa]=व्वावे 80 | Name[sk]=vvave 81 | Name[sl]=vvave 82 | Name[sv]=vvave 83 | Name[tr]=Vvave 84 | Name[uk]=vvave 85 | Name[x-test]=xxvvavexx 86 | Name[zh_CN]=vvave 87 | Name[zh_TW]=vvave 88 | 89 | [Event/Notify] 90 | Name=vvave... 91 | Name[ar]=فاف… 92 | Name[ca]=vvave... 93 | Name[ca@valencia]=vvave… 94 | Name[cs]=vvave... 95 | Name[da]=vvave... 96 | Name[de]=vvave ... 97 | Name[el]=vvave... 98 | Name[en_GB]=vvave... 99 | Name[eo]=vvave... 100 | Name[es]=vvave... 101 | Name[et]=vvave ... 102 | Name[eu]=vvave... 103 | Name[fi]=vvave… 104 | Name[fr]=Vvave... 105 | Name[gl]=vvave… 106 | Name[he]=vvave… 107 | Name[hi]=वीवेव... 108 | Name[hu]=vvave… 109 | Name[ia]=vvave... 110 | Name[id]=vvave... 111 | Name[it]=vvave... 112 | Name[ka]=vvave... 113 | Name[ko]=vvave... 114 | Name[lt]=vvave... 115 | Name[lv]=vvave... 116 | Name[nl]=vvave... 117 | Name[nn]=vvave … 118 | Name[pa]=ਵੇਵੇਵ... 119 | Name[pl]=vvave... 120 | Name[pt]=vvave... 121 | Name[pt_BR]=vvave... 122 | Name[ro]=vvave... 123 | Name[sa]=व्ववे... 124 | Name[sk]=vvave... 125 | Name[sl]=vvave... 126 | Name[sv]=vvave... 127 | Name[tr]=Vvave… 128 | Name[uk]=vvave… 129 | Name[x-test]=xxvvave...xx 130 | Name[zh_CN]=vvave... 131 | Name[zh_TW]=vvave... 132 | Comment=Connection to device failed 133 | Comment[ar]=فشل الاتّصال بالجهاز 134 | Comment[ca]=Ha fallat la connexió amb el dispositiu 135 | Comment[ca@valencia]=No s'ha pogut connectar amb el dispositiu 136 | Comment[cs]=Připojení k zařízení selhalo 137 | Comment[da]=Forbindelse til enhed mislykkedes 138 | Comment[de]=Verbindung zum Gerät fehlgeschlagen 139 | Comment[el]=Αποτυχία σύνδεσης στρη συκευή 140 | Comment[en_GB]=Connection to device failed 141 | Comment[eo]=Konekto al aparato malsukcesis 142 | Comment[es]=La conexión con el dispositivo ha fallado 143 | Comment[et]=Ühendumine seadmega nurjus 144 | Comment[eu]=Gailurako konexioak huts egin du 145 | Comment[fi]=Laitteeseen yhdistäminen epäonnistui 146 | Comment[fr]=La connexion au périphérique a échoué 147 | Comment[gl]=A conexión co dispositivo fallou. 148 | Comment[he]=החיבור להתקן נכשל 149 | Comment[hi]=उपकरण के साथ सम्पर्क विफल 150 | Comment[hu]=Nem sikerült csatlakozni az eszközhöz 151 | Comment[ia]=Connexion al dispositivo falleva 152 | Comment[id]=Koneksi ke peranti gagal 153 | Comment[it]=Connessione al dispositivo non riuscita 154 | Comment[ka]=მოწყობილობასთან შეერთების შეცდომა 155 | Comment[ko]=장치에 연결할 수 없음 156 | Comment[lt]=Ryšys su įrenginiu nutrūko 157 | Comment[lv]=Savienošanās ar ierīci nav izdevusies 158 | Comment[nl]=Verbinding met het apparaat is mislukt 159 | Comment[nn]=Klarte ikkje kopla til eininga 160 | Comment[pa]=ਡਿਵਾਈਸ ਨਾਲ ਕਨੈਕਸ਼ਨ ਅਸਫ਼ਲ ਹੈ 161 | Comment[pl]=Połączenie do urządzenia nie udało się 162 | Comment[pt]=A ligação ao dispositivo foi mal-sucedida 163 | Comment[pt_BR]=Falha na conexão do dispositivo 164 | Comment[ro]=Conectarea la dispozitiv a eșuat 165 | Comment[sa]=यन्त्रेण सह संयोजनं विफलम् अभवत् 166 | Comment[sk]=Pripojenie k zariadeniu zlyhalo 167 | Comment[sl]=Povezava z napravo ni uspela 168 | Comment[sv]=Anslutning till enhet misslyckades 169 | Comment[tr]=Aygıta bağlanılamadı 170 | Comment[uk]=Не вдалося встановити з’єднання із пристроєм 171 | Comment[x-test]=xxConnection to device failedxx 172 | Comment[zh_CN]=设备连接失败 173 | Comment[zh_TW]=連線裝置失敗 174 | Icon=vvave 175 | Action=Popup 176 | -------------------------------------------------------------------------------- /src/assets/vvave.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/vvave/ff68d3bd59e5594c2d0627bdd5e5416d7b614530/src/assets/vvave.png -------------------------------------------------------------------------------- /src/assets/vvave192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/vvave/ff68d3bd59e5594c2d0627bdd5e5416d7b614530/src/assets/vvave192.png -------------------------------------------------------------------------------- /src/assets/vvave72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/vvave/ff68d3bd59e5594c2d0627bdd5e5416d7b614530/src/assets/vvave72.png -------------------------------------------------------------------------------- /src/assets/vvave96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/vvave/ff68d3bd59e5594c2d0627bdd5e5416d7b614530/src/assets/vvave96.png -------------------------------------------------------------------------------- /src/db/Queries.js: -------------------------------------------------------------------------------- 1 | var GET = { 2 | 3 | allTracks : "select t.* from tracks t inner join albums al on al.album = t.album and al.artist = t.artist", 4 | allTracksSimple : "select * from tracks", 5 | allAlbums : "select * from albums", 6 | allAlbumsAsc : "select * from albums order by album asc", 7 | allArtists : "select * from artists", 8 | allArtistsAsc : "select * from artists order by artist asc", 9 | albumTracks_ : "select t.* from tracks t inner join albums al on al.album = t.album and al.artist = t.artist where t.album = \"%1\" and t.artist = \"%2\" order by t.track asc", 10 | artistTracks_ : "select t.* from tracks t inner join albums al on al.album = t.album and al.artist = t.artist where t.artist = \"%1\" order by t.album asc, t.track asc", 11 | albumTracksSimple_ : "select * from tracks where album = \"%1\" and artist = \"%2\"", 12 | artistTracksSimple_ : "select * from tracks where artist = \"%1\"", 13 | tracksWhere_ : "select t.* from tracks t inner join albums al on al.album = t.album and al.artist = t.artist where %1", 14 | // sourceTracks_: "select * from tracks where sources_url = \"%1\"", 15 | 16 | mostPlayedTracks : "select t.* from tracks t inner join albums al on t.album = al.album and t.artist = al.artist WHERE t.count >= 3 order by strftime(\"%s\", t.addDate) desc, t.count asc LIMIT 20", 17 | 18 | favoriteTracks : "select t.* from tracks t inner join albums al on t.album = al.album and t.artist = al.artist where rate > 0 order by rate desc limit 100", 19 | 20 | newTracks: "select t.* from (select * from tracks order by releasedate desc, strftime(\"%s\", adddate) desc limit 100) t inner join albums al on t.album = al.album and t.artist = al.artist where t.count <= 4 order by t.title asc limit 20", 21 | 22 | randomTracks: "select t.* from tracks t inner join albums al on t.album = al.album and t.artist = al.artist where t.count <= 4 order by RANDOM() limit 10", 23 | 24 | 25 | oldTracks: "select t.* from (select * from tracks where releasedate > 0 order by releasedate asc limit 100) t inner join albums al on t.album = al.album and t.artist = al.artist order by t.title asc limit 40", 26 | 27 | recentTracks: "select t.* from (select * from tracks order by strftime(\"%s\", lastsync) desc limit 10) t inner join albums al on t.album = al.album and t.artist = al.artist order by t.title asc", 28 | recentTracks_: "select t.* from (select * from tracks order by strftime(\"%s\", lastsync) desc limit 100) t inner join albums al on t.album = al.album and t.artist = al.artist order by t.title asc", 29 | 30 | recentArtists: "select distinct a.artist from (select * from tracks order by strftime(\"%s\", adddate) desc limit 100) a order by a.artist asc", 31 | recentAlbums: "select distinct a.album, a.artist from (select * from tracks order by releasedate desc, strftime(\"%s\", adddate) desc limit 100) a order by a.album asc limit 50", 32 | 33 | neverPlayedTracks: "select t.* from tracks t inner join albums al on t.album = al.album and t.artist = al.artist where t.count <= 1 order by RANDOM() limit 20", 34 | neverPlayedTracks_: "select t.* from (select * from tracks order by strftime(\"%s\", adddate) asc) t inner join albums al on t.album = al.album and t.artist = al.artist where t.count <= 1 order by t.title asc limit 100", 35 | 36 | babedTracks: "#favs", 37 | playlistTracks_ : "#%1", 38 | 39 | genres: "select distinct genre as tag from tracks", 40 | 41 | tags : "select distinct tag from tags where context = 'tag' limit 1000", 42 | trackTags : "select distinct tag from tracks_tags where context = 'tag' and tag collate nocase not in (select artist from artists) and tag in (select tag from tracks_tags group by tag having count(url) > 1) order by tag collate nocase limit 1000", 43 | albumTags_: "select distinct tag from albums_tags where context = 'tag' and album = \"%1\" and artist = \"%2\"", 44 | artistTags_: "select distinct tag from artists_tags where context = 'tag' and artist = \"%1\"", 45 | 46 | } 47 | 48 | var INSERT = { 49 | trackPlaylist_ : "insert into tracks_playlists () ", 50 | } 51 | 52 | var UPDATE = {} 53 | -------------------------------------------------------------------------------- /src/db/collectionDB.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | #include 16 | #include 17 | 18 | #include "../utils/bae.h" 19 | 20 | enum sourceTypes { LOCAL, ONLINE, DEVICE }; 21 | 22 | class CollectionDB : public QObject 23 | { 24 | Q_OBJECT 25 | 26 | public: 27 | explicit CollectionDB(QObject *parent = nullptr); 28 | static CollectionDB *getInstance(); 29 | 30 | bool insert(const QString &tableName, const QVariantMap &insertData); 31 | bool update(const QString &tableName, const FMH::MODEL &updateData, const QVariantMap &where); 32 | bool update(const QString &table, const QString &column, const QVariant &newValue, const QVariant &op, const QString &id); 33 | bool remove(const QString &table, const QString &column, const QVariantMap &where); 34 | 35 | bool execQuery(QSqlQuery &query) const; 36 | bool execQuery(const QString &queryTxt); 37 | 38 | /*basic public actions*/ 39 | bool check_existance(const QString &tableName, const QString &searchId, const QString &search); 40 | 41 | /* usefull actions */ 42 | bool addTrack(const FMH::MODEL &track); 43 | bool updateTrack(const FMH::MODEL &track); 44 | Q_INVOKABLE bool rateTrack(const QString &path, const int &value); 45 | 46 | bool lyricsTrack(const FMH::MODEL &track, const QString &value); 47 | Q_INVOKABLE bool playedTrack(const QString &url); 48 | 49 | bool albumTrack(const FMH::MODEL &track, const QString &value); 50 | bool trackTrack(const FMH::MODEL &track, const QString &value); 51 | 52 | FMH::MODEL_LIST getDBData(const QStringList &urls); 53 | FMH::MODEL_LIST getDBData(const QString &queryTxt, std::function modifier = nullptr); 54 | QVariantList getDBDataQML(const QString &queryTxt); 55 | static QStringList dataToList(const FMH::MODEL_LIST &list, const FMH::MODEL_KEY &key); 56 | 57 | FMH::MODEL_LIST getAlbumTracks(const QString &album, const QString &artist, const FMH::MODEL_KEY &orderBy = FMH::MODEL_KEY::TRACK, const BAE::W &order = BAE::W::ASC); 58 | FMH::MODEL_LIST getArtistTracks(const QString &artist, const FMH::MODEL_KEY &orderBy = FMH::MODEL_KEY::ALBUM, const BAE::W &order = BAE::W::ASC); 59 | FMH::MODEL_LIST getSearchedTracks(const FMH::MODEL_KEY &where, const QString &search); 60 | FMH::MODEL_LIST getMostPlayedTracks(const int &greaterThan = 1, const int &limit = 50, const FMH::MODEL_KEY &orderBy = FMH::MODEL_KEY::COUNT, const BAE::W &order = BAE::W::DESC); 61 | FMH::MODEL_LIST getRecentTracks(const int &limit = 50, const FMH::MODEL_KEY &orderBy = FMH::MODEL_KEY::ADDDATE, const BAE::W &order = BAE::W::DESC); 62 | 63 | int getTrackStars(const QString &path); 64 | QStringList getArtistAlbums(const QString &artist); 65 | 66 | void removeMissingTracks(); 67 | 68 | bool removeArtist(const QString &artist); 69 | bool cleanArtists(); 70 | bool removeAlbum(const QString &album, const QString &artist); 71 | bool cleanAlbums(); 72 | 73 | bool removeSource(const QString &url); 74 | bool removeTrack(const QString &path); 75 | QSqlQuery getQuery(const QString &queryTxt) const; 76 | QSqlQuery getQuery() const; 77 | 78 | /*useful tools*/ 79 | sourceTypes sourceType(const QString &url); 80 | void openDB(const QString &name); 81 | 82 | private: 83 | void prepareCollectionDB(); 84 | 85 | QString name; 86 | QSqlDatabase m_db; 87 | 88 | Q_SIGNALS: 89 | void trackInserted(QVariantMap item); 90 | 91 | void albumInserted(QVariantMap item); 92 | void albumsInserted(QVariantList items); 93 | 94 | void artistInserted(QVariantMap item); 95 | void artistsInserted(QVariantList items); 96 | 97 | void sourceInserted(QVariantMap item); 98 | void sourcesInserted(QVariantList items); 99 | 100 | void albumsCleaned(const int &amount); 101 | void artistsCleaned(const int &amount); 102 | }; 103 | -------------------------------------------------------------------------------- /src/db/script.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE ARTISTS 2 | ( 3 | artist TEXT , 4 | wiki TEXT, 5 | PRIMARY KEY(artist) 6 | ); 7 | 8 | CREATE TABLE ALBUMS 9 | ( 10 | album TEXT , 11 | artist TEXT, 12 | wiki TEXT, 13 | PRIMARY KEY(album, artist), 14 | FOREIGN KEY(artist) REFERENCES artists(artist) 15 | ); 16 | 17 | CREATE TABLE SOURCES_TYPES 18 | ( 19 | id INTEGER PRIMARY KEY, 20 | name TEXT NOT NULL 21 | ); 22 | 23 | CREATE TABLE SOURCES 24 | ( 25 | url TEXT PRIMARY KEY , 26 | sourcetype INTEGER NOT NULL, 27 | FOREIGN KEY(sourcetype) REFERENCES SOURCES_TYPES(id) 28 | ); 29 | 30 | CREATE TABLE TRACKS 31 | ( 32 | url TEXT , 33 | source TEXT , 34 | track INTEGER , 35 | title TEXT NOT NULL, 36 | artist TEXT NOT NULL, 37 | album TEXT NOT NULL, 38 | duration INTEGER , 39 | comment TEXT, 40 | count INTEGER , 41 | rate INTEGER NOT NULL, 42 | releasedate DATE , 43 | adddate DATE NOT NULL, 44 | lastsync DATE, 45 | lyrics TEXT NOT NULL, 46 | genre TEXT, 47 | wiki TEXT NOT NULL, 48 | PRIMARY KEY (url), 49 | FOREIGN KEY(source) REFERENCES SOURCES(url), 50 | FOREIGN KEY(album, artist) REFERENCES albums(album, artist) 51 | ); 52 | 53 | --First insertions 54 | 55 | INSERT INTO SOURCES_TYPES VALUES (1,"LOCAL"); 56 | INSERT INTO SOURCES_TYPES VALUES (2,"ONLINE"); 57 | INSERT INTO SOURCES_TYPES VALUES (3,"DEVICE"); 58 | -------------------------------------------------------------------------------- /src/kde/mpris2/mediaplayer2.cpp: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | SPDX-FileCopyrightText: 2014 (c) Sujith Haridasan 3 | SPDX-FileCopyrightText: 2014 (c) Ashish Madeti 4 | SPDX-FileCopyrightText: 2016 (c) Matthieu Gallien 5 | 6 | SPDX-License-Identifier: GPL-3.0-or-later 7 | ***************************************************************************/ 8 | 9 | #include "mediaplayer2.h" 10 | 11 | #include 12 | #include 13 | 14 | MediaPlayer2::MediaPlayer2(QObject *parent) 15 | : QDBusAbstractAdaptor(parent) 16 | { 17 | } 18 | 19 | MediaPlayer2::~MediaPlayer2() = default; 20 | 21 | bool MediaPlayer2::CanQuit() const 22 | { 23 | return true; 24 | } 25 | 26 | bool MediaPlayer2::CanRaise() const 27 | { 28 | return true; 29 | } 30 | bool MediaPlayer2::HasTrackList() const 31 | { 32 | return false; 33 | } 34 | 35 | void MediaPlayer2::Quit() 36 | { 37 | QCoreApplication::quit(); 38 | } 39 | 40 | void MediaPlayer2::Raise() 41 | { 42 | Q_EMIT raisePlayer(); 43 | } 44 | 45 | QString MediaPlayer2::Identity() const 46 | { 47 | return KAboutData::applicationData().displayName(); 48 | } 49 | 50 | QString MediaPlayer2::DesktopEntry() const 51 | { 52 | return KAboutData::applicationData().desktopFileName(); 53 | } 54 | 55 | QStringList MediaPlayer2::SupportedUriSchemes() const 56 | { 57 | return QStringList() << QStringLiteral("file"); 58 | } 59 | 60 | QStringList MediaPlayer2::SupportedMimeTypes() const 61 | { 62 | // KService::Ptr app = KService::serviceByDesktopName(KCmdLineArgs::aboutData()->appName()); 63 | 64 | // if (app) { 65 | // return app->mimeTypes(); 66 | // } 67 | 68 | return QStringList(); 69 | } 70 | 71 | #include "moc_mediaplayer2.cpp" 72 | -------------------------------------------------------------------------------- /src/kde/mpris2/mediaplayer2.h: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | SPDX-FileCopyrightText: 2014 (c) Sujith Haridasan 3 | SPDX-FileCopyrightText: 2014 (c) Ashish Madeti 4 | SPDX-FileCopyrightText: 2016 (c) Matthieu Gallien 5 | 6 | SPDX-License-Identifier: GPL-3.0-or-later 7 | ***************************************************************************/ 8 | 9 | #ifndef MEDIACENTER_MEDIAPLAYER2_H 10 | #define MEDIACENTER_MEDIAPLAYER2_H 11 | 12 | #include 13 | #include 14 | 15 | class MediaPlayer2 : public QDBusAbstractAdaptor 16 | { 17 | Q_OBJECT 18 | Q_CLASSINFO("D-Bus Interface", "org.mpris.MediaPlayer2") // Docs: https://specifications.freedesktop.org/mpris-spec/latest/Media_Player.html 19 | 20 | Q_PROPERTY(bool CanQuit READ CanQuit CONSTANT) 21 | Q_PROPERTY(bool CanRaise READ CanRaise CONSTANT) 22 | Q_PROPERTY(bool HasTrackList READ HasTrackList CONSTANT) 23 | 24 | Q_PROPERTY(QString Identity READ Identity CONSTANT) 25 | Q_PROPERTY(QString DesktopEntry READ DesktopEntry CONSTANT) 26 | 27 | Q_PROPERTY(QStringList SupportedUriSchemes READ SupportedUriSchemes CONSTANT) 28 | Q_PROPERTY(QStringList SupportedMimeTypes READ SupportedMimeTypes CONSTANT) 29 | 30 | public: 31 | explicit MediaPlayer2(QObject *parent = nullptr); 32 | ~MediaPlayer2() override; 33 | 34 | bool CanQuit() const; 35 | bool CanRaise() const; 36 | bool HasTrackList() const; 37 | 38 | QString Identity() const; 39 | QString DesktopEntry() const; 40 | 41 | QStringList SupportedUriSchemes() const; 42 | QStringList SupportedMimeTypes() const; 43 | 44 | public Q_SLOTS: 45 | void Quit(); 46 | void Raise(); 47 | 48 | Q_SIGNALS: 49 | void raisePlayer(); 50 | }; 51 | 52 | #endif // MEDIACENTER_MEDIAPLAYER2_H 53 | -------------------------------------------------------------------------------- /src/kde/mpris2/mediaplayer2player.h: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | SPDX-FileCopyrightText: 2014 (c) Sujith Haridasan 3 | SPDX-FileCopyrightText: 2014 (c) Ashish Madeti 4 | SPDX-FileCopyrightText: 2016 (c) Matthieu Gallien 5 | 6 | SPDX-License-Identifier: GPL-3.0-or-later 7 | ***************************************************************************/ 8 | 9 | #ifndef MEDIAPLAYER2PLAYER_H 10 | #define MEDIAPLAYER2PLAYER_H 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | class Playlist; 17 | class Player; 18 | 19 | class MediaPlayer2Player : public QDBusAbstractAdaptor 20 | { 21 | Q_OBJECT 22 | Q_CLASSINFO("D-Bus Interface", "org.mpris.MediaPlayer2.Player") // Docs: https://specifications.freedesktop.org/mpris-spec/latest/Player_Interface.html 23 | 24 | Q_PROPERTY(QString PlaybackStatus READ PlaybackStatus NOTIFY playbackStatusChanged) 25 | Q_PROPERTY(double Rate READ Rate WRITE setRate NOTIFY rateChanged) 26 | Q_PROPERTY(QVariantMap Metadata READ Metadata NOTIFY playbackStatusChanged) 27 | Q_PROPERTY(double Volume READ Volume WRITE setVolume NOTIFY volumeChanged) 28 | Q_PROPERTY(qlonglong Position READ Position WRITE setPropertyPosition NOTIFY playbackStatusChanged) 29 | Q_PROPERTY(double MinimumRate READ MinimumRate CONSTANT) 30 | Q_PROPERTY(double MaximumRate READ MaximumRate CONSTANT) 31 | Q_PROPERTY(bool CanGoNext READ CanGoNext NOTIFY canGoNextChanged) 32 | Q_PROPERTY(bool CanGoPrevious READ CanGoPrevious NOTIFY canGoPreviousChanged) 33 | Q_PROPERTY(bool CanPlay READ CanPlay NOTIFY canPlayChanged) 34 | Q_PROPERTY(bool CanPause READ CanPause NOTIFY canPauseChanged) 35 | Q_PROPERTY(bool CanControl READ CanControl NOTIFY canControlChanged) 36 | Q_PROPERTY(bool CanSeek READ CanSeek NOTIFY canSeekChanged) 37 | Q_PROPERTY(int currentTrack READ currentTrack WRITE setCurrentTrack NOTIFY currentTrackChanged) 38 | Q_PROPERTY(int mediaPlayerPresent READ mediaPlayerPresent WRITE setMediaPlayerPresent NOTIFY mediaPlayerPresentChanged) 39 | 40 | public: 41 | explicit MediaPlayer2Player(Playlist *playListControler, Player *audioPlayer, bool showProgressOnTaskBar, QObject *parent = nullptr); 42 | ~MediaPlayer2Player() override; 43 | 44 | QString PlaybackStatus() const; 45 | double Rate() const; 46 | QVariantMap Metadata() const; 47 | double Volume() const; 48 | qlonglong Position() const; 49 | double MinimumRate() const; 50 | double MaximumRate() const; 51 | bool CanGoNext() const; 52 | bool CanGoPrevious() const; 53 | bool CanPlay() const; 54 | bool CanPause() const; 55 | bool CanSeek() const; 56 | bool CanControl() const; 57 | int currentTrack() const; 58 | int mediaPlayerPresent() const; 59 | 60 | bool showProgressOnTaskBar() const; 61 | void setShowProgressOnTaskBar(bool value); 62 | 63 | Q_SIGNALS: 64 | void Seeked(qlonglong Position); 65 | 66 | void rateChanged(double newRate); 67 | void volumeChanged(double newVol); 68 | void playbackStatusChanged(); 69 | void canGoNextChanged(); 70 | void canGoPreviousChanged(); 71 | void canPlayChanged(); 72 | void canPauseChanged(); 73 | void canControlChanged(); 74 | void canSeekChanged(); 75 | void currentTrackChanged(); 76 | void mediaPlayerPresentChanged(); 77 | void next(); 78 | void previous(); 79 | void playPause(); 80 | void stop(); 81 | 82 | public Q_SLOTS: 83 | 84 | void emitSeeked(int pos); 85 | 86 | void Next(); 87 | void Previous(); 88 | void Pause(); 89 | void PlayPause(); 90 | void Stop(); 91 | void Play(); 92 | void Seek(qlonglong Offset); 93 | void SetPosition(const QDBusObjectPath &trackId, qlonglong pos); 94 | void OpenUri(const QString &uri); 95 | 96 | private Q_SLOTS: 97 | 98 | void playerSourceChanged(); 99 | 100 | void playControlEnabledChanged(); 101 | 102 | void skipBackwardControlEnabledChanged(); 103 | 104 | void skipForwardControlEnabledChanged(); 105 | 106 | void playerPlaybackStateChanged(); 107 | 108 | void playerIsSeekableChanged(); 109 | 110 | void audioPositionChanged(); 111 | 112 | void audioDurationChanged(); 113 | 114 | void playerVolumeChanged(); 115 | 116 | private: 117 | void signalPropertiesChange(const QString &property, const QVariant &value); 118 | 119 | void setMediaPlayerPresent(int status); 120 | void setRate(double newRate); 121 | void setVolume(double volume); 122 | void setPropertyPosition(int newPositionInMs); 123 | void setCurrentTrack(int newTrackPosition); 124 | 125 | QVariantMap getMetadataOfCurrentTrack(); 126 | 127 | QVariantMap m_metadata; 128 | QString m_currentTrack; 129 | QString m_currentTrackId; 130 | double m_rate = 1.0; 131 | double m_volume = 0.0; 132 | int m_mediaPlayerPresent = 0; 133 | bool m_canPlay = false; 134 | bool m_canGoNext = false; 135 | bool m_canGoPrevious = false; 136 | qlonglong m_position = 0; 137 | Playlist *m_playListControler = nullptr; 138 | bool m_playerIsSeekableChanged = false; 139 | Player *m_audioPlayer = nullptr; 140 | mutable QDBusMessage mProgressIndicatorSignal; 141 | int mPreviousProgressPosition = 0; 142 | bool mShowProgressOnTaskBar = true; 143 | }; 144 | 145 | #endif // MEDIAPLAYER2PLAYER_H 146 | -------------------------------------------------------------------------------- /src/kde/mpris2/mpris2.cpp: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | SPDX-FileCopyrightText: 2014 (c) Sujith Haridasan 3 | SPDX-FileCopyrightText: 2014 (c) Ashish Madeti 4 | SPDX-FileCopyrightText: 2016 (c) Matthieu Gallien 5 | 6 | SPDX-License-Identifier: GPL-3.0-or-later 7 | ***************************************************************************/ 8 | 9 | #include "mpris2.h" 10 | #include "../../services/local/player.h" 11 | #include "../../services/local/playlist.h" 12 | 13 | #if (defined Q_OS_LINUX || defined Q_OS_FREEBSD) && !defined Q_OS_ANDROID 14 | #include "mediaplayer2.h" 15 | #include "mediaplayer2player.h" 16 | #include 17 | #endif 18 | 19 | #if defined Q_OS_WIN 20 | #include 21 | #else 22 | #include 23 | #endif 24 | 25 | Mpris2::Mpris2(QObject *parent) 26 | : QObject(parent) 27 | { 28 | } 29 | 30 | void Mpris2::initDBusService() 31 | { 32 | #if (defined Q_OS_LINUX || defined Q_OS_FREEBSD) && !defined Q_OS_ANDROID 33 | 34 | QString mspris2Name(QStringLiteral("org.mpris.MediaPlayer2.") + m_playerName); 35 | 36 | bool success = QDBusConnection::sessionBus().registerService(mspris2Name); 37 | 38 | // If the above failed, it's likely because we're not the first instance 39 | // or the name is already taken. In that event the MPRIS2 spec wants the 40 | // following: 41 | if (!success) { 42 | #if defined Q_OS_WIN 43 | success = QDBusConnection::sessionBus().registerService(mspris2Name + QLatin1String(".instance") + QString::number(GetCurrentProcessId())); 44 | #else 45 | success = QDBusConnection::sessionBus().registerService(mspris2Name + QLatin1String(".instance") + QString::number(getpid())); 46 | #endif 47 | } 48 | 49 | if (success) { 50 | m_mp2 = std::make_unique(this); 51 | m_mp2p = std::make_unique(m_playListModel, m_audioPlayer, mShowProgressOnTaskBar, this); 52 | 53 | QDBusConnection::sessionBus().registerObject(QStringLiteral("/org/mpris/MediaPlayer2"), this, QDBusConnection::ExportAdaptors); 54 | 55 | connect(m_mp2.get(), &MediaPlayer2::raisePlayer, this, &Mpris2::raisePlayer); 56 | } 57 | #endif 58 | } 59 | 60 | Mpris2::~Mpris2() = default; 61 | 62 | QString Mpris2::playerName() const 63 | { 64 | return m_playerName; 65 | } 66 | 67 | Playlist *Mpris2::playListModel() const 68 | { 69 | return m_playListModel; 70 | } 71 | 72 | Player *Mpris2::audioPlayer() const 73 | { 74 | return m_audioPlayer; 75 | } 76 | 77 | bool Mpris2::showProgressOnTaskBar() const 78 | { 79 | return mShowProgressOnTaskBar; 80 | } 81 | 82 | void Mpris2::setPlayerName(const QString &playerName) 83 | { 84 | if (m_playerName == playerName) { 85 | return; 86 | } 87 | 88 | m_playerName = playerName; 89 | 90 | #if (defined Q_OS_LINUX || defined Q_OS_FREEBSD) && !defined Q_OS_ANDROID 91 | if (m_playListModel && m_audioPlayer && m_audioPlayer && !m_playerName.isEmpty()) { 92 | if (!m_mp2) { 93 | initDBusService(); 94 | } 95 | } 96 | #endif 97 | 98 | Q_EMIT playerNameChanged(); 99 | } 100 | 101 | void Mpris2::setPlayListModel(Playlist *playListModel) 102 | { 103 | if (m_playListModel == playListModel) { 104 | return; 105 | } 106 | 107 | m_playListModel = playListModel; 108 | 109 | #if (defined Q_OS_LINUX || defined Q_OS_FREEBSD) && !defined Q_OS_ANDROID 110 | 111 | if (m_playListModel && m_audioPlayer && m_audioPlayer && !m_playerName.isEmpty()) { 112 | if (!m_mp2) { 113 | initDBusService(); 114 | } 115 | } 116 | #endif 117 | Q_EMIT playListModelChanged(); 118 | } 119 | 120 | void Mpris2::setAudioPlayer(Player *audioPlayer) 121 | { 122 | if (m_audioPlayer == audioPlayer) 123 | return; 124 | 125 | m_audioPlayer = audioPlayer; 126 | #if (defined Q_OS_LINUX || defined Q_OS_FREEBSD) && !defined Q_OS_ANDROID 127 | 128 | if (m_playListModel && m_audioPlayer && m_audioPlayer && !m_playerName.isEmpty()) { 129 | if (!m_mp2) { 130 | initDBusService(); 131 | } 132 | } 133 | #endif 134 | Q_EMIT audioPlayerChanged(); 135 | } 136 | 137 | void Mpris2::setShowProgressOnTaskBar(bool value) 138 | { 139 | #if (defined Q_OS_LINUX || defined Q_OS_FREEBSD) && !defined Q_OS_ANDROID 140 | m_mp2p->setShowProgressOnTaskBar(value); 141 | mShowProgressOnTaskBar = value; 142 | Q_EMIT showProgressOnTaskBarChanged(); 143 | #else 144 | Q_UNUSED(value) 145 | #endif 146 | } 147 | 148 | #include "moc_mpris2.cpp" 149 | -------------------------------------------------------------------------------- /src/kde/mpris2/mpris2.h: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | SPDX-FileCopyrightText: 2014 (c) Sujith Haridasan 3 | SPDX-FileCopyrightText: 2014 (c) Ashish Madeti 4 | SPDX-FileCopyrightText: 2016 (c) Matthieu Gallien 5 | 6 | SPDX-License-Identifier: GPL-3.0-or-later 7 | ***************************************************************************/ 8 | #pragma once 9 | 10 | #include 11 | #include 12 | #include 13 | 14 | class MediaPlayer2Player; 15 | class MediaPlayer2; 16 | 17 | class Player; 18 | class Playlist; 19 | 20 | class Mpris2 : public QObject 21 | { 22 | Q_OBJECT 23 | 24 | Q_PROPERTY(QString playerName READ playerName WRITE setPlayerName NOTIFY playerNameChanged) 25 | 26 | Q_PROPERTY(Playlist *playListModel READ playListModel WRITE setPlayListModel NOTIFY playListModelChanged) 27 | 28 | Q_PROPERTY(Player *audioPlayer READ audioPlayer WRITE setAudioPlayer NOTIFY audioPlayerChanged) 29 | 30 | Q_PROPERTY(bool showProgressOnTaskBar READ showProgressOnTaskBar WRITE setShowProgressOnTaskBar NOTIFY showProgressOnTaskBarChanged) 31 | 32 | public: 33 | explicit Mpris2(QObject *parent = nullptr); 34 | ~Mpris2() override; 35 | 36 | QString playerName() const; 37 | 38 | Playlist *playListModel() const; 39 | 40 | Player *audioPlayer() const; 41 | 42 | bool showProgressOnTaskBar() const; 43 | 44 | public Q_SLOTS: 45 | 46 | void setPlayerName(const QString &playerName); 47 | 48 | void setPlayListModel(Playlist *playListModel); 49 | 50 | void setAudioPlayer(Player *audioPlayer); 51 | 52 | void setShowProgressOnTaskBar(bool value); 53 | 54 | Q_SIGNALS: 55 | void raisePlayer(); 56 | 57 | void playerNameChanged(); 58 | 59 | void playListModelChanged(); 60 | 61 | void audioPlayerChanged(); 62 | 63 | void showProgressOnTaskBarChanged(); 64 | 65 | private: 66 | void initDBusService(); 67 | 68 | #if (defined Q_OS_LINUX || defined Q_OS_FREEBSD) && !defined Q_OS_ANDROID 69 | std::unique_ptr m_mp2; 70 | std::unique_ptr m_mp2p; 71 | #endif 72 | 73 | QString m_playerName; 74 | Playlist *m_playListModel = nullptr; 75 | Player *m_audioPlayer = nullptr; 76 | bool mShowProgressOnTaskBar = true; 77 | }; 78 | 79 | -------------------------------------------------------------------------------- /src/kde/server.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #if (defined Q_OS_LINUX || defined Q_OS_FREEBSD) && !defined Q_OS_ANDROID 5 | class OrgKdeVvaveActionsInterface; 6 | 7 | namespace AppInstance 8 | { 9 | QVector, QStringList>> appInstances(const QString& preferredService); 10 | 11 | bool attachToExistingInstance(const QList& inputUrls, const QString& preferredService = QString()); 12 | 13 | bool registerService(); 14 | } 15 | #endif 16 | 17 | class Server : public QObject 18 | { 19 | Q_OBJECT 20 | Q_CLASSINFO("D-Bus Interface", "org.kde.vvave.Actions") 21 | 22 | public: 23 | explicit Server(QObject *parent = nullptr); 24 | void setQmlObject(QObject *object); 25 | 26 | public Q_SLOTS: 27 | /** 28 | * Tries to raise/activate the Dolphin window. 29 | */ 30 | void activateWindow(); 31 | 32 | /** Stores all settings and quits Dolphin. */ 33 | void quit(); 34 | 35 | void openFiles(const QStringList &urls); 36 | /** 37 | * Determines if a URL is open in any tab. 38 | * @note Use of QString instead of QUrl is required to be callable via DBus. 39 | * 40 | * @param url URL to look for 41 | * @returns true if url is currently open in a tab, false otherwise. 42 | */ 43 | bool isUrlOpen(const QString &url); 44 | 45 | 46 | private: 47 | QObject* m_qmlObject = nullptr; 48 | QStringList filterFiles(const QStringList &urls); 49 | 50 | }; 51 | 52 | -------------------------------------------------------------------------------- /src/macos_files/vvave.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/vvave/ff68d3bd59e5594c2d0627bdd5e5416d7b614530/src/macos_files/vvave.icns -------------------------------------------------------------------------------- /src/models/albums/albumsmodel.cpp: -------------------------------------------------------------------------------- 1 | #include "albumsmodel.h" 2 | #include "db/collectionDB.h" 3 | 4 | #include "vvave.h" 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | AlbumsModel::AlbumsModel(QObject *parent) : MauiList(parent) 12 | { 13 | qRegisterMetaType("FMH::MODEL_LIST"); 14 | qRegisterMetaType("FMH::MODEL"); 15 | } 16 | 17 | void AlbumsModel::componentComplete() 18 | { 19 | auto timer = new QTimer(this); 20 | timer->setSingleShot(true); 21 | timer->setInterval(1000); 22 | 23 | if (query == QUERY::ALBUMS) { 24 | 25 | connect(CollectionDB::getInstance(), &CollectionDB::albumInserted, [this, timer](QVariantMap) { 26 | m_newAlbums++; 27 | timer->start(); 28 | }); 29 | 30 | connect(timer, &QTimer::timeout, [this]() 31 | { 32 | if (m_newAlbums > 0) { 33 | this->setList(); 34 | m_newAlbums = 0; 35 | } 36 | }); 37 | 38 | } else { 39 | 40 | connect(CollectionDB::getInstance(), &CollectionDB::artistInserted, [this, timer](QVariantMap) { 41 | m_newAlbums++; 42 | timer->start(); 43 | }); 44 | 45 | connect(timer, &QTimer::timeout, [this]() 46 | { 47 | if (m_newAlbums > 0) { 48 | this->setList(); 49 | m_newAlbums = 0; 50 | } 51 | }); 52 | } 53 | 54 | connect(vvave::instance(), &vvave::sourceRemoved, this, &AlbumsModel::setList); 55 | connect(this, &AlbumsModel::queryChanged, this, &AlbumsModel::setList); 56 | setList(); 57 | } 58 | 59 | const FMH::MODEL_LIST &AlbumsModel::items() const 60 | { 61 | return this->list; 62 | } 63 | 64 | void AlbumsModel::setQuery(const QUERY &query) 65 | { 66 | if (this->query == query) 67 | return; 68 | 69 | this->query = query; 70 | Q_EMIT this->queryChanged(); 71 | } 72 | 73 | AlbumsModel::QUERY AlbumsModel::getQuery() const 74 | { 75 | return this->query; 76 | } 77 | 78 | void AlbumsModel::setList() 79 | { 80 | Q_EMIT this->preListChanged(); 81 | 82 | QString m_Query; 83 | if (this->query == AlbumsModel::QUERY::ALBUMS) 84 | m_Query = "select * from albums order by album asc"; 85 | else if (this->query == AlbumsModel::QUERY::ARTISTS) 86 | m_Query = "select * from artists order by artist asc"; 87 | else 88 | return; 89 | 90 | this->list = CollectionDB::getInstance()->getDBData(m_Query); 91 | 92 | Q_EMIT this->postListChanged(); 93 | Q_EMIT this->countChanged(); 94 | } 95 | 96 | void AlbumsModel::refresh() 97 | { 98 | this->setList(); 99 | } 100 | 101 | int AlbumsModel::indexOfName(const QString &query) 102 | { 103 | const auto it = std::find_if(this->items().constBegin(), this->items().constEnd(), [&](const FMH::MODEL &item) -> bool { 104 | return item[this->query == AlbumsModel::QUERY::ALBUMS ? FMH::MODEL_KEY::ALBUM : FMH::MODEL_KEY::ARTIST].startsWith(query, Qt::CaseInsensitive); 105 | }); 106 | 107 | if (it != this->items().constEnd()) 108 | return (std::distance(this->items().constBegin(), it)); 109 | else 110 | return -1; 111 | } 112 | -------------------------------------------------------------------------------- /src/models/albums/albumsmodel.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | class AlbumsModel : public MauiList 9 | { 10 | Q_OBJECT 11 | Q_PROPERTY(AlbumsModel::QUERY query READ getQuery WRITE setQuery NOTIFY queryChanged()) 12 | 13 | public: 14 | enum QUERY : uint_fast8_t { ARTISTS = FMH::MODEL_KEY::ARTIST, ALBUMS = FMH::MODEL_KEY::ALBUM }; 15 | Q_ENUM(QUERY) 16 | 17 | explicit AlbumsModel(QObject *parent = nullptr); 18 | 19 | void componentComplete() override; 20 | 21 | const FMH::MODEL_LIST &items() const override; 22 | 23 | void setQuery(const AlbumsModel::QUERY &query); 24 | AlbumsModel::QUERY getQuery() const; 25 | 26 | private: 27 | FMH::MODEL_LIST list; 28 | 29 | void setList(); 30 | 31 | AlbumsModel::QUERY query; 32 | 33 | int m_newAlbums; 34 | 35 | public Q_SLOTS: 36 | void refresh(); 37 | int indexOfName(const QString &query); 38 | 39 | Q_SIGNALS: 40 | void queryChanged(); 41 | }; 42 | -------------------------------------------------------------------------------- /src/models/cloud/cloud.cpp: -------------------------------------------------------------------------------- 1 | #include "cloud.h" 2 | 3 | #include 4 | 5 | #include "NextCloud/nextmusic.h" 6 | #include "abstractmusicprovider.h" 7 | 8 | #include 9 | 10 | Cloud::Cloud(QObject *parent) : MauiList(parent) 11 | , provider(new NextMusic(this)) 12 | { 13 | connect(MauiAccounts::instance(), &MauiAccounts::currentAccountChanged, [this](QVariantMap account) { 14 | this->provider->setCredentials(FMH::toModel(account)); 15 | this->setList(); 16 | }); 17 | 18 | connect(provider, &AbstractMusicProvider::collectionReady, [=](FMH::MODEL_LIST data) { 19 | Q_EMIT this->albumsChanged(); 20 | Q_EMIT this->artistsChanged(); 21 | 22 | Q_EMIT this->preListChanged(); 23 | this->list = data; 24 | this->sortList(); 25 | Q_EMIT this->postListChanged(); 26 | }); 27 | 28 | connect(static_cast(provider), &NextMusic::trackPathReady, [=](QString id, QString path) { 29 | auto track = static_cast(provider)->getTrackItem(id); 30 | track[FMH::MODEL_KEY::URL] = path; 31 | Q_EMIT this->fileReady(FMH::toMap(track)); 32 | }); 33 | } 34 | 35 | void Cloud::componentComplete() 36 | { 37 | this->provider->setCredentials(FMH::toModel(MauiAccounts::instance()->getCurrentAccount())); 38 | this->setList(); 39 | } 40 | 41 | void Cloud::setSortBy(const Cloud::SORTBY &sort) 42 | { 43 | if (this->sort == sort) 44 | return; 45 | 46 | this->sort = sort; 47 | 48 | Q_EMIT this->preListChanged(); 49 | this->sortList(); 50 | Q_EMIT this->postListChanged(); 51 | Q_EMIT this->sortByChanged(); 52 | } 53 | 54 | Cloud::SORTBY Cloud::getSortBy() const 55 | { 56 | return this->sort; 57 | } 58 | 59 | QVariantList Cloud::getAlbums() const 60 | { 61 | return this->provider->getAlbumsList(); 62 | } 63 | 64 | QVariantList Cloud::getArtists() const 65 | { 66 | return this->provider->getArtistsList(); 67 | } 68 | 69 | const FMH::MODEL_LIST &Cloud::items() const 70 | { 71 | return this->list; 72 | } 73 | 74 | void Cloud::setList() 75 | { 76 | this->provider->getCollection(); 77 | } 78 | 79 | void Cloud::sortList() 80 | { 81 | if (this->sort == Cloud::SORTBY::NONE) 82 | return; 83 | 84 | const auto key = static_cast(this->sort); 85 | std::sort(this->list.begin(), this->list.end(), [key](const FMH::MODEL &e1, const FMH::MODEL &e2) -> bool { 86 | switch (key) { 87 | case FMH::MODEL_KEY::RELEASEDATE: 88 | case FMH::MODEL_KEY::RATE: 89 | case FMH::MODEL_KEY::FAV: 90 | case FMH::MODEL_KEY::COUNT: { 91 | if (e1[key].toInt() > e2[key].toInt()) 92 | return true; 93 | break; 94 | } 95 | 96 | case FMH::MODEL_KEY::TRACK: { 97 | if (e1[key].toInt() < e2[key].toInt()) 98 | return true; 99 | break; 100 | } 101 | 102 | case FMH::MODEL_KEY::ADDDATE: { 103 | auto currentTime = QDateTime::currentDateTime(); 104 | 105 | auto date1 = QDateTime::fromString(e1[key], Qt::TextDate); 106 | auto date2 = QDateTime::fromString(e2[key], Qt::TextDate); 107 | 108 | if (date1.secsTo(currentTime) < date2.secsTo(currentTime)) 109 | return true; 110 | 111 | break; 112 | } 113 | 114 | case FMH::MODEL_KEY::TITLE: 115 | case FMH::MODEL_KEY::ARTIST: 116 | case FMH::MODEL_KEY::ALBUM: 117 | case FMH::MODEL_KEY::FORMAT: { 118 | const auto str1 = QString(e1[key]).toLower(); 119 | const auto str2 = QString(e2[key]).toLower(); 120 | 121 | if (str1 < str2) 122 | return true; 123 | break; 124 | } 125 | 126 | default: 127 | if (e1[key] < e2[key]) 128 | return true; 129 | } 130 | 131 | return false; 132 | }); 133 | } 134 | 135 | void Cloud::upload(const QUrl &url) 136 | { 137 | Q_UNUSED(url) 138 | } 139 | 140 | void Cloud::getFileUrl(const QString &id) 141 | { 142 | static_cast(this->provider)->getTrackPath(id); 143 | } 144 | 145 | void Cloud::getFileUrl(const int &index) 146 | { 147 | if (index >= this->list.size() || index < 0) 148 | return; 149 | 150 | this->getFileUrl(this->list.at((index))[FMH::MODEL_KEY::ID]); 151 | } 152 | -------------------------------------------------------------------------------- /src/models/cloud/cloud.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | class FM; 9 | class AbstractMusicProvider; 10 | class Cloud : public MauiList 11 | { 12 | Q_OBJECT 13 | Q_PROPERTY(Cloud::SORTBY sortBy READ getSortBy WRITE setSortBy NOTIFY sortByChanged) 14 | Q_PROPERTY(QVariantList artists READ getArtists NOTIFY artistsChanged) 15 | Q_PROPERTY(QVariantList albums READ getAlbums NOTIFY albumsChanged) 16 | 17 | public: 18 | enum SORTBY : uint_fast8_t { 19 | ADDDATE = FMH::MODEL_KEY::ADDDATE, 20 | RELEASEDATE = FMH::MODEL_KEY::RELEASEDATE, 21 | FORMAT = FMH::MODEL_KEY::FORMAT, 22 | ARTIST = FMH::MODEL_KEY::ARTIST, 23 | TITLE = FMH::MODEL_KEY::TITLE, 24 | ALBUM = FMH::MODEL_KEY::ALBUM, 25 | RATE = FMH::MODEL_KEY::RATE, 26 | FAV = FMH::MODEL_KEY::FAV, 27 | TRACK = FMH::MODEL_KEY::TRACK, 28 | COUNT = FMH::MODEL_KEY::COUNT, 29 | NONE 30 | 31 | }; 32 | Q_ENUM(SORTBY) 33 | 34 | explicit Cloud(QObject *parent = nullptr); 35 | void componentComplete() override final; 36 | 37 | const FMH::MODEL_LIST &items() const override; 38 | 39 | void setSortBy(const Cloud::SORTBY &sort); 40 | Cloud::SORTBY getSortBy() const; 41 | 42 | QVariantList getAlbums() const; 43 | QVariantList getArtists() const; 44 | 45 | private: 46 | AbstractMusicProvider *provider; 47 | FMH::MODEL_LIST list; 48 | void sortList(); 49 | void setList(); 50 | 51 | Cloud::SORTBY sort = Cloud::SORTBY::ARTIST; 52 | 53 | public Q_SLOTS: 54 | void upload(const QUrl &url); 55 | 56 | void getFileUrl(const QString &id); 57 | void getFileUrl(const int &index); 58 | 59 | Q_SIGNALS: 60 | void sortByChanged(); 61 | void fileReady(QVariantMap track); 62 | void warning(QString error); 63 | 64 | void artistsChanged(); 65 | void albumsChanged(); 66 | }; 67 | -------------------------------------------------------------------------------- /src/models/folders/foldersmodel.cpp: -------------------------------------------------------------------------------- 1 | #include "foldersmodel.h" 2 | #include 3 | 4 | FoldersModel::FoldersModel(QObject *parent) : MauiList(parent) 5 | {} 6 | 7 | const FMH::MODEL_LIST &FoldersModel::items() const 8 | { 9 | return this->list; 10 | } 11 | 12 | void FoldersModel::setFolders(const QList &folders) 13 | { 14 | if(m_folders == folders) 15 | return; 16 | 17 | m_folders = folders; 18 | 19 | Q_EMIT foldersChanged(); 20 | } 21 | 22 | QList FoldersModel::folders() const 23 | { 24 | return m_folders; 25 | } 26 | 27 | 28 | void FoldersModel::componentComplete() 29 | { 30 | connect(this, &FoldersModel::foldersChanged, this, &FoldersModel::setList); 31 | this->setList(); 32 | } 33 | 34 | void FoldersModel::setList() 35 | { 36 | if(m_folders.isEmpty()) 37 | { 38 | return; 39 | } 40 | 41 | Q_EMIT this->preListChanged(); 42 | this->list.clear(); 43 | 44 | for(const auto &folder : std::as_const(m_folders)) 45 | { 46 | this->list << FMStatic::getFileInfoModel(folder); 47 | } 48 | Q_EMIT this->postListChanged(); 49 | Q_EMIT this->countChanged(); 50 | } 51 | -------------------------------------------------------------------------------- /src/models/folders/foldersmodel.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | class FoldersModel : public MauiList 8 | { 9 | Q_OBJECT 10 | Q_PROPERTY(QList folders READ folders WRITE setFolders NOTIFY foldersChanged) 11 | 12 | public: 13 | FoldersModel(QObject *parent = nullptr); 14 | 15 | const FMH::MODEL_LIST &items() const override final; 16 | void setFolders(const QList &folders); 17 | QList folders () const; 18 | void componentComplete() override final; 19 | 20 | private: 21 | FMH::MODEL_LIST list; 22 | QList m_folders; 23 | 24 | void setList(); 25 | 26 | Q_SIGNALS: 27 | void foldersChanged(); 28 | 29 | }; 30 | -------------------------------------------------------------------------------- /src/models/playlists/playlistsmodel.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | class Tagging; 5 | class PlaylistsModel : public MauiList 6 | { 7 | Q_OBJECT 8 | Q_PROPERTY(int limit READ limit WRITE setLimit NOTIFY limitChanged) 9 | 10 | public: 11 | explicit PlaylistsModel(QObject *parent = nullptr); 12 | ~PlaylistsModel(); 13 | const FMH::MODEL_LIST &items() const override; 14 | 15 | int limit() const; 16 | void setLimit(int newLimit); 17 | 18 | void componentComplete() override; 19 | 20 | public Q_SLOTS: 21 | void insert(const QString &playlist); 22 | 23 | void addTrack(const QString &playlist, const QStringList &urls); 24 | void removeTrack(const QString &playlist, const QString &url); 25 | void removePlaylist(const int &index); 26 | 27 | private: 28 | Tagging *m_tagging; 29 | FMH::MODEL_LIST list; 30 | void setList(); 31 | 32 | FMH::MODEL_LIST defaultPlaylists(); 33 | FMH::MODEL_LIST tags(); 34 | FMH::MODEL packPlaylist(const QString &playlist); 35 | QString playlistArtworkPreviews(const QString &playlist); 36 | 37 | int m_limit = 9999; 38 | 39 | Q_SIGNALS: 40 | void sortByChanged(); 41 | void fileTagged(QUrl url, QString playlist); 42 | 43 | void limitChanged(); 44 | }; 45 | 46 | -------------------------------------------------------------------------------- /src/models/tracks/overviewmodel.cpp: -------------------------------------------------------------------------------- 1 | #include "overviewmodel.h" 2 | 3 | OverviewModel::OverviewModel(QObject *parent) : QObject(parent) 4 | { 5 | 6 | } 7 | -------------------------------------------------------------------------------- /src/models/tracks/overviewmodel.h: -------------------------------------------------------------------------------- 1 | #ifndef OVERVIEWMODEL_H 2 | #define OVERVIEWMODEL_H 3 | 4 | #include 5 | 6 | class OverviewModel : public QObject 7 | { 8 | Q_OBJECT 9 | public: 10 | explicit OverviewModel(QObject *parent = nullptr); 11 | 12 | signals: 13 | 14 | }; 15 | 16 | #endif // OVERVIEWMODEL_H 17 | -------------------------------------------------------------------------------- /src/models/tracks/tracksmodel.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | class TracksModel : public MauiList 8 | { 9 | Q_OBJECT 10 | Q_PROPERTY(QString query READ getQuery WRITE setQuery NOTIFY queryChanged) 11 | Q_PROPERTY(int limit READ limit WRITE setLimit NOTIFY limitChanged) 12 | 13 | public: 14 | explicit TracksModel(QObject *parent = nullptr); 15 | 16 | void componentComplete() override final; 17 | const FMH::MODEL_LIST &items() const override final; 18 | 19 | void setQuery(const QString &query); 20 | QString getQuery() const; 21 | 22 | int limit() const; 23 | void setLimit(int limit); 24 | 25 | private: 26 | FMH::MODEL_LIST list; 27 | QString query; 28 | int m_limit = 99999; 29 | int m_newTracks; 30 | 31 | void setList(); 32 | 33 | Q_SIGNALS: 34 | void queryChanged(); 35 | void limitChanged(int limit); 36 | 37 | public Q_SLOTS: 38 | bool append(const QVariantMap &item); 39 | bool appendUrl(const QUrl &url); 40 | 41 | bool insertUrl(const QString &url, const int &index); 42 | bool insertUrls(const QStringList &urls, const int &index); 43 | 44 | bool appendUrls(const QStringList &urls); 45 | bool appendAt(const QVariantMap &item, const int &at); 46 | bool appendQuery(const QString &query); 47 | 48 | void copy(const TracksModel *list); 49 | 50 | void clear(); 51 | bool fav(const int &index, const bool &value); 52 | bool countUp(const int &index); 53 | bool remove(const int &index); 54 | bool erase(const int &index); 55 | bool removeMissing(const int &index); 56 | 57 | void refresh(); 58 | bool update(const QVariantMap &data, const int &index); 59 | 60 | void updateMetadata(const QVariantMap &data, const int &index); 61 | 62 | bool move(const int &index, const int &to); 63 | 64 | QStringList urls() const; 65 | }; 66 | 67 | -------------------------------------------------------------------------------- /src/pulpo/enums.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | namespace PULPO 11 | { 12 | enum class SERVICES : uint8_t { LastFm, Spotify, iTunes, MusicBrainz, Genius, LyricWikia, Wikipedia, WikiLyrics, Deezer, ALL, NONE }; 13 | 14 | enum class ONTOLOGY : uint8_t { ARTIST, ALBUM, TRACK }; 15 | 16 | enum class INFO : uint8_t { ARTWORK, WIKI, TAGS, METADATA, LYRICS, ALL, NONE }; 17 | 18 | /*Generic context names. It's encouraged to use these instead of a unkown string*/ 19 | enum class PULPO_CONTEXT : uint8_t { 20 | TRACK_STAT, 21 | TRACK_NUMBER, 22 | TRACK_TITLE, 23 | TRACK_DATE, 24 | TRACK_TEAM, 25 | TRACK_AUTHOR, 26 | TRACK_LANGUAGE, 27 | TRACK_SIMILAR, 28 | 29 | ALBUM_TEAM, 30 | ALBUM_STAT, 31 | ALBUM_TITLE, 32 | ALBUM_DATE, 33 | ALBUM_LANGUAGE, 34 | ALBUM_SIMILAR, 35 | ALBUM_LABEL, 36 | 37 | ARTIST_STAT, 38 | ARTIST_TITLE, 39 | ARTIST_DATE, 40 | ARTIST_LANGUAGE, 41 | ARTIST_PLACE, 42 | ARTIST_SIMILAR, 43 | ARTIST_TEAM, 44 | ARTIST_ALIAS, 45 | ARTIST_GENDER, 46 | 47 | GENRE, 48 | TAG, 49 | WIKI, 50 | IMAGE, 51 | LYRIC, 52 | SOURCE 53 | 54 | }; 55 | 56 | static const QMap CONTEXT_MAP = {{PULPO_CONTEXT::ALBUM_STAT, "album_stat"}, 57 | {PULPO_CONTEXT::ALBUM_TITLE, "album_title"}, 58 | {PULPO_CONTEXT::ALBUM_DATE, "album_date"}, 59 | {PULPO_CONTEXT::ALBUM_LANGUAGE, "album_language"}, 60 | {PULPO_CONTEXT::ALBUM_SIMILAR, "album_similar"}, 61 | {PULPO_CONTEXT::ALBUM_LABEL, "album_label"}, 62 | {PULPO_CONTEXT::ALBUM_TEAM, "album_team"}, 63 | 64 | {PULPO_CONTEXT::ARTIST_STAT, "artist_stat"}, 65 | {PULPO_CONTEXT::ARTIST_TITLE, "artist_title"}, 66 | {PULPO_CONTEXT::ARTIST_DATE, "artist_date"}, 67 | {PULPO_CONTEXT::ARTIST_LANGUAGE, "artist_language"}, 68 | {PULPO_CONTEXT::ARTIST_PLACE, "artist_place"}, 69 | {PULPO_CONTEXT::ARTIST_SIMILAR, "artist_similar"}, 70 | {PULPO_CONTEXT::ARTIST_ALIAS, "artist_alias"}, 71 | {PULPO_CONTEXT::ARTIST_GENDER, "artist_gender"}, 72 | {PULPO_CONTEXT::ARTIST_TEAM, "artist_team"}, 73 | 74 | {PULPO_CONTEXT::TRACK_STAT, "track_stat"}, 75 | {PULPO_CONTEXT::TRACK_DATE, "track_date"}, 76 | {PULPO_CONTEXT::TRACK_TITLE, "track_title"}, 77 | {PULPO_CONTEXT::TRACK_NUMBER, "track_number"}, 78 | {PULPO_CONTEXT::TRACK_TEAM, "track_team"}, 79 | {PULPO_CONTEXT::TRACK_AUTHOR, "track_author"}, 80 | {PULPO_CONTEXT::TRACK_LANGUAGE, "track_language"}, 81 | {PULPO_CONTEXT::TRACK_SIMILAR, "track_similar"}, 82 | 83 | {PULPO_CONTEXT::GENRE, "genre"}, 84 | {PULPO_CONTEXT::TAG, "tag"}, 85 | {PULPO_CONTEXT::WIKI, "wiki"}, 86 | {PULPO_CONTEXT::IMAGE, "image"}, 87 | {PULPO_CONTEXT::LYRIC, "lyric"}, 88 | {PULPO_CONTEXT::SOURCE, "source"} 89 | 90 | }; 91 | 92 | enum class RECURSIVE : bool { ON = true, OFF = false }; 93 | 94 | typedef QMap VALUE; 95 | typedef QMap INFO_K; 96 | // typedef QMap RESPONSE; 97 | 98 | typedef QMap> SCOPE; 99 | 100 | struct RESPONSE { 101 | PULPO_CONTEXT context; 102 | QVariant value; 103 | }; 104 | typedef QList RESPONSES; 105 | 106 | struct REQUEST { 107 | FMH::MODEL track; 108 | 109 | PULPO::ONTOLOGY ontology; 110 | QList info; 111 | QList services; 112 | 113 | std::function callback = nullptr; 114 | }; 115 | } 116 | 117 | -------------------------------------------------------------------------------- /src/pulpo/htmlparser.cpp: -------------------------------------------------------------------------------- 1 | #include "htmlparser.h" 2 | 3 | htmlParser::htmlParser(QObject *parent) 4 | : QObject(parent) 5 | { 6 | } 7 | 8 | void htmlParser::setHtml(const QByteArray &array) 9 | { 10 | this->html = array; 11 | } 12 | 13 | QString htmlParser::extractProp(const QString &tag, const QString &prop) 14 | { 15 | // qDebug()<<"extractProp"<html).split(">")); 28 | 29 | for (auto i = 0; i < html.size(); i++) { 30 | QString tag = html.at(i); 31 | tag += ">"; 32 | 33 | if (findTag(tag, "<" + tagRef + ">") && tag.contains(attribute)) { 34 | QString subResult; 35 | // qDebug()< html.size()) 42 | break; 43 | } 44 | 45 | results << subResult.simplified(); 46 | qDebug() << subResult; 47 | } 48 | } 49 | 50 | return results; 51 | } 52 | 53 | /*QStringList htmlParser::parseTag_old(const QString &tagRef) 54 | { 55 | QString html(this->html); 56 | QStringList tags; 57 | int i =0; 58 | while(html.size()>i) 59 | { 60 | if(html.at(i)=="<") 61 | { 62 | QString tag; 63 | 64 | while(html.at(i)!=">") 65 | { 66 | tag+=html.at(i); 67 | i++; 68 | if(i>html.size()) break; 69 | } 70 | tag+=">"; 71 | 72 | 73 | if(findTag(tag,tagRef)) 74 | { 75 | tags<html.size()) break; 83 | } 84 | 85 | return tags; 86 | }*/ 87 | 88 | bool htmlParser::findTag(const QString &txt, const QString &tagRef) 89 | { 90 | // qDebug()<<"size of tag<<"< txt.size()) 99 | break; 100 | } 101 | subTag += ">"; 102 | } 103 | 104 | i++; 105 | if (i > txt.size()) 106 | break; 107 | } 108 | 109 | if (tagRef == subTag) 110 | return true; 111 | else 112 | return false; 113 | } 114 | -------------------------------------------------------------------------------- /src/pulpo/htmlparser.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | class htmlParser : public QObject 9 | { 10 | Q_OBJECT 11 | public: 12 | explicit htmlParser(QObject *parent = nullptr); 13 | void setHtml(const QByteArray &html); 14 | QStringList parseTag(const QString &tagRef, const QString &attribute = ""); // return all tag matches with content 15 | bool findTag(const QString &txt, const QString &tagRef); 16 | QString extractProp(const QString &tag, const QString &prop); 17 | 18 | private: 19 | QByteArray html; 20 | 21 | Q_SIGNALS: 22 | void finishedParsingTags(const QStringList &tags); 23 | void finishedExtractingProp(const QString &prop); 24 | }; 25 | -------------------------------------------------------------------------------- /src/pulpo/pulpo.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Babe - tiny music player 3 | Copyright (C) 2017 Camilo Higuita 4 | This program is free software; you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation; either version 3 of the License, or 7 | (at your option) any later version. 8 | This program is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | GNU General Public License for more details. 12 | You should have received a copy of the GNU General Public License 13 | along with this program; if not, write to the Free Software Foundation, 14 | Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 15 | 16 | */ 17 | 18 | #include "pulpo.h" 19 | #include "services/lastfmService.h" 20 | #include "services/spotifyService.h" 21 | //#include "services/lyricwikiaService.h" 22 | //#include "services/geniusService.h" 23 | //#include "services/musicbrainzService.h" 24 | //#include "services/deezerService.h" 25 | 26 | //#include "qgumbodocument.h"p 27 | //#include "qgumbonode.h" 28 | 29 | Pulpo::Pulpo(QObject *parent) 30 | : QObject(parent) 31 | { 32 | } 33 | 34 | Pulpo::~Pulpo() 35 | { 36 | qDebug() << "DELETING PULPO INSTANCE"; 37 | } 38 | 39 | void Pulpo::request(const PULPO::REQUEST &request) 40 | { 41 | this->req = request; 42 | 43 | if (this->req.track.isEmpty()) { 44 | Q_EMIT this->error(); 45 | return; 46 | } 47 | 48 | if (this->req.services.isEmpty()) { 49 | qWarning() << "Please register at least one Pulpo Service"; 50 | Q_EMIT this->error(); 51 | return; 52 | } 53 | 54 | this->start(); 55 | } 56 | 57 | void Pulpo::start() 58 | { 59 | this->send(this->req.services.first()); 60 | } 61 | 62 | void Pulpo::passSignal(const REQUEST &request, const RESPONSES &responses) 63 | { 64 | if (request.callback) 65 | request.callback(request, responses); 66 | else 67 | Q_EMIT this->infoReady(request, responses); 68 | Q_EMIT this->finished(); 69 | } 70 | 71 | void Pulpo::send(const SERVICES &service) 72 | { 73 | switch (service) { 74 | case SERVICES::LastFm: { 75 | auto lastfm = new class lastfm(); 76 | connect(lastfm, &lastfm::responseReady, [this, lastfm](PULPO::REQUEST request, PULPO::RESPONSES responses) { 77 | this->passSignal(request, responses); 78 | lastfm->deleteLater(); 79 | }); 80 | 81 | connect(lastfm, &lastfm::error, [this, service, lastfm](PULPO::REQUEST request) { 82 | if (!request.services.isEmpty()) { 83 | request.services.removeOne(service); 84 | this->request(request); 85 | } else { 86 | Q_EMIT this->error(); 87 | } 88 | 89 | lastfm->deleteLater(); 90 | }); 91 | 92 | lastfm->set(this->req); 93 | break; 94 | } 95 | 96 | case SERVICES::Spotify: { 97 | auto spotify = new class spotify(); 98 | connect(spotify, &lastfm::responseReady, [this, spotify](PULPO::REQUEST request, PULPO::RESPONSES responses) { 99 | this->passSignal(request, responses); 100 | spotify->deleteLater(); 101 | }); 102 | 103 | connect(spotify, &lastfm::error, [this, service, spotify](PULPO::REQUEST request) { 104 | if (!request.services.isEmpty()) { 105 | request.services.removeOne(service); 106 | this->request(request); 107 | } else { 108 | Q_EMIT this->error(); 109 | } 110 | 111 | spotify->deleteLater(); 112 | }); 113 | 114 | spotify->set(this->req); 115 | break; 116 | } 117 | 118 | default: { 119 | if (!this->req.services.isEmpty()) { 120 | this->req.services.removeOne(service); 121 | this->request(req); 122 | } else { 123 | Q_EMIT this->error(); 124 | } 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/pulpo/pulpo.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "../utils/bae.h" 13 | #include "enums.h" 14 | 15 | using namespace PULPO; 16 | 17 | class Pulpo : public QObject 18 | { 19 | Q_OBJECT 20 | 21 | public: 22 | explicit Pulpo(QObject *parent = nullptr); 23 | ~Pulpo(); 24 | 25 | void request(const PULPO::REQUEST &request); 26 | 27 | private: 28 | void start(); 29 | QList services = {}; 30 | 31 | PULPO::REQUEST req; 32 | 33 | void passSignal(const REQUEST &request, const RESPONSES &responses); 34 | void send(const SERVICES &service); 35 | 36 | Q_SIGNALS: 37 | void infoReady(PULPO::REQUEST request, PULPO::RESPONSES responses); 38 | void error(); 39 | void finished(); 40 | }; 41 | -------------------------------------------------------------------------------- /src/pulpo/service.cpp: -------------------------------------------------------------------------------- 1 | #include "service.h" 2 | #include 3 | 4 | Service::Service(QObject *parent) 5 | : QObject(parent) 6 | { 7 | } 8 | 9 | void Service::set(const PULPO::REQUEST &request) 10 | { 11 | this->request = request; 12 | } 13 | 14 | void Service::parseArtist(const QByteArray &) 15 | { 16 | 17 | } 18 | 19 | void Service::parseAlbum(const QByteArray &) 20 | { 21 | 22 | } 23 | 24 | void Service::parseTrack(const QByteArray &) 25 | { 26 | 27 | } 28 | 29 | void Service::parse(const QByteArray &array) 30 | { 31 | switch (this->request.ontology) { 32 | case PULPO::ONTOLOGY::ALBUM: 33 | this->parseAlbum(array); 34 | break; 35 | case PULPO::ONTOLOGY::ARTIST: 36 | this->parseArtist(array); 37 | break; 38 | case PULPO::ONTOLOGY::TRACK: 39 | this->parseTrack(array); 40 | break; 41 | } 42 | } 43 | 44 | void Service::retrieve(const QString &url, const QMap &headers) 45 | { 46 | if (!url.isEmpty()) { 47 | auto downloader = new FMH::Downloader; 48 | connect(downloader, &FMH::Downloader::dataReady, [this, downloader](QByteArray array) { 49 | Q_EMIT this->arrayReady(array); 50 | downloader->deleteLater(); 51 | }); 52 | downloader->getArray(QUrl(url), headers); 53 | } 54 | } 55 | 56 | bool Service::scopePass() 57 | { 58 | auto info = this->request.info; 59 | for (const auto inf : info) { 60 | if (!this->scope[this->request.ontology].contains(inf)) { 61 | info.removeAll(inf); 62 | } 63 | } 64 | 65 | return !info.isEmpty(); 66 | } 67 | -------------------------------------------------------------------------------- /src/pulpo/service.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "../utils/bae.h" 10 | #include "enums.h" 11 | 12 | #define ERROR(request) \ 13 | { \ 14 | Q_EMIT this->error(request); \ 15 | return; \ 16 | } 17 | 18 | class Service : public QObject 19 | { 20 | Q_OBJECT 21 | 22 | private: 23 | public: 24 | explicit Service(QObject *parent = nullptr); 25 | 26 | protected: 27 | PULPO::REQUEST request; // the main request. the track info, the ontology and info type 28 | PULPO::SCOPE scope; // what ontology and info can the service parse 29 | PULPO::RESPONSES responses; 30 | 31 | void parse(const QByteArray &array); 32 | 33 | virtual void set(const PULPO::REQUEST &request); 34 | 35 | virtual void parseArtist(const QByteArray &); 36 | virtual void parseAlbum(const QByteArray &); 37 | virtual void parseTrack(const QByteArray &); 38 | 39 | void retrieve(const QString &url, const QMap &headers = {}); 40 | 41 | static PULPO::RESPONSE packResponse(const PULPO::ONTOLOGY &ontology, const PULPO::INFO &info, const PULPO::VALUE &value); 42 | 43 | bool scopePass(); 44 | 45 | Q_SIGNALS: 46 | void arrayReady(QByteArray array); 47 | void responseReady(PULPO::REQUEST request, PULPO::RESPONSES responses); 48 | void error(PULPO::REQUEST request); 49 | }; 50 | -------------------------------------------------------------------------------- /src/pulpo/services/deezerService.h: -------------------------------------------------------------------------------- 1 | #ifndef DEEZERSERVICE_H 2 | #define DEEZERSERVICE_H 3 | 4 | #include "../pulpo.h" 5 | #include 6 | 7 | class deezer : public Pulpo 8 | { 9 | Q_OBJECT 10 | 11 | private: 12 | const QString API = "https://api.deezer.com/search?q="; 13 | 14 | QString getID(const QString &url); 15 | bool getAlbumInfo(const QByteArray &array); 16 | bool extractLyrics(const QByteArray &array); 17 | 18 | public: 19 | explicit deezer(const FMH::MODEL &song); 20 | virtual bool setUpService(const PULPO::ONTOLOGY &ontology, const PULPO::INFO &info); 21 | 22 | protected: 23 | virtual bool parseArtist(); 24 | virtual bool parseAlbum(); 25 | virtual bool parseTrack(); 26 | }; 27 | 28 | #endif // DEEZERSERVICE_H 29 | -------------------------------------------------------------------------------- /src/pulpo/services/geniusService.h: -------------------------------------------------------------------------------- 1 | #ifndef GENIUSSERVICE_H 2 | #define GENIUSSERVICE_H 3 | 4 | #include "../pulpo.h" 5 | #include 6 | 7 | class genius : public Pulpo 8 | { 9 | Q_OBJECT 10 | 11 | private: 12 | const QString KEY = "Bearer UARllo5N6CLQYVlqFwolyauSlYiyU_07YTg7HGHkWRbimN4GWPJehPP5fzu9lXeO"; 13 | const QString API = "https://api.genius.com"; 14 | 15 | QString getID(const QString &url); 16 | bool getAlbumInfo(const QByteArray &array); 17 | bool extractLyrics(const QByteArray &array); 18 | 19 | public: 20 | explicit genius(const FMH::MODEL &song); 21 | virtual bool setUpService(const PULPO::ONTOLOGY &ontology, const PULPO::INFO &info); 22 | 23 | protected: 24 | virtual bool parseArtist(); 25 | virtual bool parseAlbum() 26 | { 27 | return false; 28 | } 29 | virtual bool parseTrack(); 30 | }; 31 | 32 | #endif // GENIUSSERVICE_H 33 | -------------------------------------------------------------------------------- /src/pulpo/services/lastfmService.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../service.h" 4 | #include 5 | 6 | class lastfm : public Service 7 | { 8 | Q_OBJECT 9 | 10 | private: 11 | inline static const QString API = "http://ws.audioscrobbler.com/2.0/"; 12 | inline static const QString KEY = "&api_key=ba6f0bd3c887da9101c10a50cf2af133"; 13 | 14 | void parseSimilar(); 15 | 16 | public: 17 | explicit lastfm(); 18 | ~lastfm() override; 19 | 20 | void set(const PULPO::REQUEST &request) override final; 21 | 22 | protected: 23 | virtual void parseArtist(const QByteArray &array) override final; 24 | virtual void parseAlbum(const QByteArray &array) override final; 25 | // virtual void parseTrack(const QByteArray &array); 26 | 27 | /*INTERNAL IMPLEMENTATION*/ 28 | }; 29 | -------------------------------------------------------------------------------- /src/pulpo/services/lyricwikiaService.cpp: -------------------------------------------------------------------------------- 1 | #include "lyricwikiaService.h" 2 | 3 | lyricWikia::lyricWikia(const FMH::MODEL &song) 4 | { 5 | this->availableInfo.insert(ONTOLOGY::TRACK, {INFO::LYRICS}); 6 | this->track = song; 7 | 8 | connect(this, &lyricWikia::arrayReady, [this](QByteArray data) { 9 | qDebug() << "GOT THE ARRAY"; 10 | this->array = data; 11 | this->parseArray(); 12 | }); 13 | } 14 | 15 | lyricWikia::~lyricWikia() 16 | { 17 | } 18 | 19 | bool lyricWikia::setUpService(const PULPO::ONTOLOGY &ontology, const PULPO::INFO &info) 20 | { 21 | this->ontology = ontology; 22 | this->info = info; 23 | 24 | if (!this->availableInfo[this->ontology].contains(this->info)) 25 | return false; 26 | 27 | auto url = this->API; 28 | 29 | switch (this->ontology) { 30 | case PULPO::ONTOLOGY::TRACK: { 31 | QUrl encodedArtist(this->track[FMH::MODEL_KEY::ARTIST]); 32 | encodedArtist.toEncoded(QUrl::FullyEncoded); 33 | 34 | QUrl encodedTrack(this->track[FMH::MODEL_KEY::TITLE]); 35 | encodedTrack.toEncoded(QUrl::FullyEncoded); 36 | 37 | url.append("&artist=" + encodedArtist.toString()); 38 | url.append("&song=" + encodedTrack.toString()); 39 | url.append("&fmt=xml"); 40 | 41 | break; 42 | } 43 | 44 | default: 45 | return false; 46 | } 47 | 48 | qDebug() << "[lyricwikia service]: " << url; 49 | 50 | this->startConnectionAsync(url); 51 | 52 | return true; 53 | } 54 | 55 | bool lyricWikia::parseTrack() 56 | { 57 | QString xmlData(this->array); 58 | 59 | QDomDocument doc; 60 | 61 | if (!doc.setContent(xmlData)) 62 | return false; 63 | 64 | QString temp = doc.documentElement().namedItem("url").toElement().text().toLatin1(); 65 | QUrl temp_u(temp); 66 | temp_u.toEncoded(QUrl::FullyEncoded); 67 | 68 | temp = temp_u.toString(); 69 | 70 | temp.replace("http://lyrics.fandom.com/", "http://lyrics.fandom.com/index.php?title="); 71 | temp.append("&action=edit"); 72 | QRegExp url_regexp("(.*)"); 73 | url_regexp.setMinimal(true); 74 | QUrl url = QUrl::fromEncoded(temp.toLatin1()); 75 | QString referer = url_regexp.cap(1); 76 | 77 | auto downloader = new FMH::Downloader; 78 | connect(downloader, &FMH::Downloader::dataReady, [=](QByteArray data) { 79 | qDebug() << "Receiving lyrics" << data; 80 | 81 | if (data.isEmpty()) 82 | return; 83 | 84 | this->extractLyrics(data); 85 | 86 | downloader->deleteLater(); 87 | }); 88 | downloader->getArray(QUrl(url).toEncoded(), {{"Referer", referer.toLatin1()}}); 89 | 90 | return true; 91 | } 92 | 93 | bool lyricWikia::extractLyrics(const QByteArray &array) 94 | { 95 | QString content = QString::fromUtf8(array.constData()); 96 | content.replace("<", "<"); 97 | QRegExp lyrics_regexp("(.*)"); 98 | lyrics_regexp.indexIn(content); 99 | QString lyrics = lyrics_regexp.cap(1); 100 | 101 | if (lyrics.isEmpty()) 102 | return false; 103 | 104 | lyrics = lyrics.trimmed(); 105 | lyrics.replace("\n", "
"); 106 | 107 | QString text; 108 | 109 | if (!lyrics.contains("PUT LYRICS HERE") && !lyrics.isEmpty()) { 110 | text = "

" + this->track[FMH::MODEL_KEY::TITLE] + "

"; 111 | text += lyrics; 112 | 113 | text = "
" + text + "
"; 114 | } 115 | 116 | emit this->infoReady(this->track, this->packResponse(ONTOLOGY::TRACK, INFO::LYRICS, CONTEXT::LYRIC, text)); 117 | return true; 118 | } 119 | -------------------------------------------------------------------------------- /src/pulpo/services/lyricwikiaService.h: -------------------------------------------------------------------------------- 1 | #ifndef LYRICWIKIASERVICE_H 2 | #define LYRICWIKIASERVICE_H 3 | #include "../pulpo.h" 4 | #include 5 | 6 | class lyricWikia : public Pulpo 7 | { 8 | Q_OBJECT 9 | 10 | private: 11 | const QString API = "https://lyrics.fandom.com/api.php?action=lyrics"; 12 | 13 | bool extractLyrics(const QByteArray &array); 14 | 15 | public: 16 | explicit lyricWikia(const FMH::MODEL &song); 17 | ~lyricWikia(); 18 | virtual bool setUpService(const ONTOLOGY &ontology, const INFO &info); 19 | 20 | protected: 21 | virtual bool parseTrack(); 22 | }; 23 | 24 | #endif // LYRICWIKIASERVICE_H 25 | -------------------------------------------------------------------------------- /src/pulpo/services/musicbrainzService.h: -------------------------------------------------------------------------------- 1 | #ifndef MUSICBRAINZSERVICE_H 2 | #define MUSICBRAINZSERVICE_H 3 | #include "../pulpo.h" 4 | #include 5 | 6 | using namespace BAE; 7 | 8 | class musicBrainz : public Pulpo 9 | { 10 | Q_OBJECT 11 | 12 | private: 13 | const QString API = "http://musicbrainz.org/ws/2/"; 14 | const QMap header = {{"User-Agent", "Babe/1.0 ( babe.kde.org )"}}; 15 | 16 | public: 17 | explicit musicBrainz(const FMH::MODEL &song); 18 | virtual bool setUpService(const PULPO::ONTOLOGY &ontology, const PULPO::INFO &info); 19 | 20 | protected: 21 | virtual bool parseArtist(); 22 | virtual bool parseAlbum(); 23 | virtual bool parseTrack(); 24 | }; 25 | 26 | #endif // MUSICBRAINZSERVICE_H 27 | -------------------------------------------------------------------------------- /src/pulpo/services/spotifyService.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../service.h" 4 | #include 5 | 6 | class spotify : public Service 7 | { 8 | Q_OBJECT 9 | 10 | private: 11 | inline static const QString API = "https://api.spotify.com/v1/search?q="; 12 | inline static const QString CLIENT_ID = "a49552c9276745f5b4752250c2d84367"; 13 | inline static const QString CLIENT_SECRET = "b3f1562559f3405dbcde4a435f50089a"; 14 | 15 | public: 16 | explicit spotify(); 17 | void set(const PULPO::REQUEST &request) override final; 18 | 19 | protected: 20 | virtual void parseArtist(const QByteArray &array) override final; 21 | virtual void parseAlbum(const QByteArray &array) override final; 22 | virtual void parseTrack(const QByteArray &array) override final; 23 | }; 24 | 25 | -------------------------------------------------------------------------------- /src/services/local/artworkprovider.cpp: -------------------------------------------------------------------------------- 1 | #include "artworkprovider.h" 2 | 3 | #include "../../utils/bae.h" 4 | #include "taginfo.h" 5 | #include "vvave.h" 6 | 7 | #include 8 | 9 | #include 10 | 11 | AsyncImageResponse::AsyncImageResponse(const QString &id, const QSize &requestedSize) 12 | : m_id(id) 13 | , m_requestedSize(requestedSize) 14 | { 15 | auto parts = id.split(":"); 16 | 17 | if (parts.isEmpty()) { 18 | m_image = QImage(":/assets/cover.png"); 19 | Q_EMIT this->finished(); 20 | return; 21 | } 22 | 23 | auto type = parts[0]; 24 | 25 | QString artist, album; 26 | 27 | if (parts.length() >= 2) 28 | artist = parts[1]; 29 | 30 | if (parts.length() >= 3) 31 | album = parts[2]; 32 | 33 | FMH::MODEL_KEY m_type = FMH::MODEL_KEY::ID; 34 | if (type == "artist") { 35 | m_type = FMH::MODEL_KEY::ARTIST; 36 | } else { 37 | m_type = FMH::MODEL_KEY::ALBUM; 38 | } 39 | 40 | FMH::MODEL data = {{FMH::MODEL_KEY::ARTIST, artist}, {FMH::MODEL_KEY::ALBUM, album}}; 41 | 42 | if (BAE::artworkCache(data, m_type)) { 43 | qDebug() << "ARTWORK CACHED" << album << artist; 44 | m_image = QImage(QUrl(data[FMH::MODEL_KEY::ARTWORK]).toLocalFile()); 45 | Q_EMIT this->finished(); 46 | } else if (vvave::instance()->fetchArtwork()) { 47 | auto m_artworkFetcher = new ArtworkFetcher; 48 | connect(m_artworkFetcher, &ArtworkFetcher::finished, m_artworkFetcher, &ArtworkFetcher::deleteLater); 49 | 50 | connect(m_artworkFetcher, &ArtworkFetcher::artworkReady, [this, m_artworkFetcher](QUrl url) { 51 | qDebug() << "FILE ARTWORK READY" << url; 52 | if (url.isEmpty() || !url.isLocalFile()) { 53 | m_image = QImage(":/assets/cover.png"); 54 | } else { 55 | this->m_image = QImage(url.toLocalFile()); 56 | } 57 | 58 | Q_EMIT this->finished(); 59 | m_artworkFetcher->deleteLater(); 60 | }); 61 | 62 | m_artworkFetcher->fetch(data, m_type == FMH::MODEL_KEY::ALBUM ? PULPO::ONTOLOGY::ALBUM : PULPO::ONTOLOGY::ARTIST); 63 | } else { 64 | m_image = QImage(":/assets/cover.png"); 65 | Q_EMIT this->finished(); 66 | } 67 | } 68 | 69 | QQuickTextureFactory *AsyncImageResponse::textureFactory() const 70 | { 71 | return QQuickTextureFactory::textureFactoryForImage(m_image); 72 | } 73 | 74 | QQuickImageResponse *ArtworkProvider::requestImageResponse(const QString &id, const QSize &requestedSize) 75 | { 76 | AsyncImageResponse *response = new AsyncImageResponse(id, requestedSize); 77 | return response; 78 | } 79 | 80 | void ArtworkFetcher::fetch(FMH::MODEL data, PULPO::ONTOLOGY ontology) 81 | { 82 | qDebug() << "FETCHING ARTWORKS FROM THREAD"; 83 | PULPO::REQUEST request; 84 | request.track = data; 85 | request.ontology = ontology; 86 | request.services = {PULPO::SERVICES::LastFm, PULPO::SERVICES::Spotify}; 87 | request.info = {PULPO::INFO::ARTWORK}; 88 | request.callback = [&](PULPO::REQUEST request, PULPO::RESPONSES responses) { 89 | qDebug() << "DONE WITH " << request.track; 90 | 91 | for (const auto &res : responses) { 92 | if (res.context == PULPO::PULPO_CONTEXT::IMAGE) { 93 | auto imageUrl = res.value.toString(); 94 | 95 | if (!imageUrl.isEmpty()) { 96 | auto downloader = new FMH::Downloader; 97 | QObject::connect(downloader, &FMH::Downloader::fileSaved, [&, downloader](QString path) mutable { 98 | downloader->deleteLater(); 99 | Q_EMIT this->artworkReady(QUrl::fromLocalFile(path)); 100 | }); 101 | 102 | const auto format = res.value.toUrl().fileName().endsWith(".png") ? ".png" : ".jpg"; 103 | QString name = !request.track[FMH::MODEL_KEY::ALBUM].isEmpty() ? request.track[FMH::MODEL_KEY::ARTIST] + "_" + request.track[FMH::MODEL_KEY::ALBUM] : request.track[FMH::MODEL_KEY::ARTIST]; 104 | 105 | BAE::fixArtworkImageFileName(name); 106 | 107 | downloader->downloadFile(QUrl(imageUrl), QUrl(BAE::CachePath.toString() + name + format)); 108 | qDebug() << "SAVING ARTWORK FOR: " << request.track[FMH::MODEL_KEY::ALBUM] << BAE::CachePath.toString() + name + format; 109 | 110 | } else { 111 | Q_EMIT this->artworkReady(QUrl(":/assets/cover.png")); 112 | } 113 | } 114 | } 115 | }; 116 | 117 | auto pulpo = new Pulpo; 118 | QObject::connect(pulpo, &Pulpo::finished, pulpo, &Pulpo::deleteLater); 119 | QObject::connect(pulpo, &Pulpo::error, [this, pulpo]() { 120 | Q_EMIT this->artworkReady(QUrl(":/assets/cover.png")); 121 | pulpo->deleteLater(); 122 | }); 123 | 124 | pulpo->request(request); 125 | } 126 | -------------------------------------------------------------------------------- /src/services/local/artworkprovider.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "pulpo/pulpo.h" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | class ArtworkFetcher : public QObject 11 | { 12 | Q_OBJECT 13 | 14 | public: 15 | void fetch(FMH::MODEL data, PULPO::ONTOLOGY ontology); 16 | 17 | Q_SIGNALS: 18 | void artworkReady(const QUrl &url); 19 | void finished(); 20 | }; 21 | 22 | class AsyncImageResponse : public QQuickImageResponse 23 | { 24 | public: 25 | AsyncImageResponse(const QString &id, const QSize &requestedSize); 26 | QQuickTextureFactory *textureFactory() const override; 27 | 28 | private: 29 | QString m_id; 30 | QSize m_requestedSize; 31 | QImage m_image; 32 | QThread m_worker; 33 | }; 34 | 35 | class ArtworkProvider : public QQuickAsyncImageProvider 36 | { 37 | public: 38 | QQuickImageResponse *requestImageResponse(const QString &id, const QSize &requestedSize) override; 39 | 40 | // void updateArtwork(const int index, const QString &artwork); 41 | // void startFetchingArtwork(FMH::MODEL_LIST data, PULPO::ONTOLOGY ontology); 42 | }; 43 | -------------------------------------------------------------------------------- /src/services/local/metadataeditor.cpp: -------------------------------------------------------------------------------- 1 | #include "metadataeditor.h" 2 | #include "taginfo.h" 3 | 4 | MetadataEditor::MetadataEditor(QObject *parent) : QObject(parent) 5 | , m_tag(new TagInfo(this)) 6 | { 7 | connect(this, &MetadataEditor::urlChanged, this, &MetadataEditor::getData); 8 | } 9 | 10 | QUrl MetadataEditor::url() const 11 | { 12 | return this->m_url; 13 | } 14 | 15 | QString MetadataEditor::title() const 16 | { 17 | return this->m_title; 18 | } 19 | 20 | QString MetadataEditor::artist() const 21 | { 22 | return this->m_artist; 23 | } 24 | 25 | QString MetadataEditor::album() const 26 | { 27 | return this->m_album; 28 | } 29 | 30 | QString MetadataEditor::genre() const 31 | { 32 | return this->m_genre; 33 | } 34 | 35 | int MetadataEditor::track() const 36 | { 37 | return this->m_track; 38 | } 39 | 40 | QString MetadataEditor::comment() const 41 | { 42 | return m_comment; 43 | } 44 | 45 | int MetadataEditor::year() const 46 | { 47 | return m_year; 48 | } 49 | 50 | void MetadataEditor::setUrl(QUrl url) 51 | { 52 | if (m_url == url) 53 | return; 54 | 55 | m_url = url; 56 | Q_EMIT urlChanged(m_url); 57 | } 58 | 59 | void MetadataEditor::setTitle(QString title) 60 | { 61 | if (m_title == title) 62 | return; 63 | 64 | m_title = title; 65 | m_tag->setTitle(m_title); 66 | Q_EMIT titleChanged(m_title); 67 | } 68 | 69 | void MetadataEditor::setArtist(QString artist) 70 | { 71 | if (m_artist == artist) 72 | return; 73 | 74 | m_artist = artist; 75 | m_tag->setArtist(m_artist); 76 | Q_EMIT artistChanged(m_artist); 77 | } 78 | 79 | void MetadataEditor::setAlbum(QString album) 80 | { 81 | if (m_album == album) 82 | return; 83 | 84 | m_album = album; 85 | m_tag->setAlbum(m_album); 86 | Q_EMIT albumChanged(m_album); 87 | } 88 | 89 | void MetadataEditor::setTrack(int track) 90 | { 91 | if (m_track == track) 92 | return; 93 | 94 | m_track = track; 95 | m_tag->setTrack(m_track); 96 | Q_EMIT trackChanged(m_track); 97 | } 98 | 99 | void MetadataEditor::setGenre(QString genre) 100 | { 101 | if (m_genre == genre) 102 | return; 103 | 104 | m_genre = genre; 105 | m_tag->setGenre(m_genre); 106 | Q_EMIT genreChanged(m_genre); 107 | } 108 | 109 | void MetadataEditor::setComment(QString comment) 110 | { 111 | if (m_comment == comment) 112 | return; 113 | 114 | m_comment = comment; 115 | m_tag->setComment(m_comment); 116 | Q_EMIT commentChanged(m_comment); 117 | } 118 | 119 | void MetadataEditor::setYear(int year) 120 | { 121 | if (m_year == year) 122 | return; 123 | 124 | m_year = year; 125 | m_tag->setYear(m_year); 126 | Q_EMIT yearChanged(m_year); 127 | } 128 | 129 | void MetadataEditor::getData() 130 | { 131 | m_tag->setFile(this->m_url.toLocalFile()); 132 | 133 | m_title = m_tag->getTitle(); 134 | Q_EMIT this->titleChanged(m_title); 135 | 136 | m_artist = m_tag->getArtist(); 137 | Q_EMIT this->artistChanged(m_artist); 138 | 139 | m_album = m_tag->getAlbum(); 140 | Q_EMIT this->albumChanged(m_album); 141 | 142 | m_genre = m_tag->getGenre(); 143 | Q_EMIT this->genreChanged(m_genre); 144 | 145 | m_track = m_tag->getTrack(); 146 | Q_EMIT this->trackChanged(m_track); 147 | 148 | m_year = m_tag->getYear(); 149 | Q_EMIT this->yearChanged(m_year); 150 | 151 | m_comment = m_tag->getComment(); 152 | Q_EMIT this->commentChanged(m_comment); 153 | } 154 | -------------------------------------------------------------------------------- /src/services/local/metadataeditor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | class TagInfo; 8 | class MetadataEditor : public QObject 9 | { 10 | Q_OBJECT 11 | Q_PROPERTY(QUrl url READ url WRITE setUrl NOTIFY urlChanged) 12 | Q_PROPERTY(QString title READ title WRITE setTitle NOTIFY titleChanged) 13 | Q_PROPERTY(QString artist READ artist WRITE setArtist NOTIFY artistChanged) 14 | Q_PROPERTY(QString album READ album WRITE setAlbum NOTIFY albumChanged) 15 | Q_PROPERTY(int track READ track WRITE setTrack NOTIFY trackChanged) 16 | Q_PROPERTY(QString genre READ genre WRITE setGenre NOTIFY genreChanged) 17 | Q_PROPERTY(QString comment READ comment WRITE setComment NOTIFY commentChanged) 18 | Q_PROPERTY(int year READ year WRITE setYear NOTIFY yearChanged) 19 | 20 | public: 21 | explicit MetadataEditor(QObject *parent = nullptr); 22 | 23 | QUrl url() const; 24 | QString title() const; 25 | QString artist() const; 26 | QString album() const; 27 | QString genre() const; 28 | int track() const; 29 | 30 | QString comment() const; 31 | 32 | int year() const; 33 | 34 | public Q_SLOTS: 35 | void setUrl(QUrl url); 36 | 37 | void setTitle(QString title); 38 | 39 | void setArtist(QString artist); 40 | 41 | void setAlbum(QString album); 42 | 43 | void setTrack(int track); 44 | 45 | void setGenre(QString genre); 46 | 47 | void setComment(QString comment); 48 | 49 | void setYear(int year); 50 | 51 | private: 52 | TagInfo *m_tag; 53 | void getData(); 54 | 55 | QUrl m_url; 56 | 57 | QString m_title; 58 | 59 | QString m_artist; 60 | 61 | QString m_album; 62 | 63 | int m_track; 64 | 65 | QString m_genre; 66 | 67 | QString m_comment; 68 | 69 | int m_year; 70 | 71 | Q_SIGNALS: 72 | void urlChanged(QUrl url); 73 | void titleChanged(QString title); 74 | void artistChanged(QString artist); 75 | void albumChanged(QString album); 76 | void trackChanged(int track); 77 | void genreChanged(QString genre); 78 | void commentChanged(QString comment); 79 | void yearChanged(int year); 80 | }; 81 | -------------------------------------------------------------------------------- /src/services/local/player.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | class PowerManagementInterface; 8 | class Player : public QObject 9 | { 10 | Q_OBJECT 11 | Q_PROPERTY(QUrl url READ getUrl WRITE setUrl NOTIFY urlChanged) 12 | Q_PROPERTY(qreal volume READ getVolume WRITE setVolume NOTIFY volumeChanged) 13 | Q_PROPERTY(QMediaPlayer::PlaybackState state READ getState NOTIFY stateChanged) 14 | Q_PROPERTY(int duration READ getDuration NOTIFY durationChanged) 15 | Q_PROPERTY(bool playing READ getPlaying NOTIFY playingChanged) 16 | Q_PROPERTY(int pos READ getPos WRITE setPos NOTIFY posChanged) 17 | 18 | public: 19 | explicit Player(QObject *parent = nullptr); 20 | 21 | void setUrl(const QUrl &value); 22 | QUrl getUrl() const; 23 | 24 | void setVolume(const qreal &value); 25 | qreal getVolume() const; 26 | 27 | int getDuration() const; 28 | 29 | QMediaPlayer::PlaybackState getState() const; 30 | bool getPlaying() const; 31 | 32 | int getPos() const; 33 | void setPos(const int &value); 34 | 35 | public Q_SLOTS: 36 | static QString transformTime(const int &value); 37 | void stop(); 38 | 39 | bool play() const; 40 | void pause() const; 41 | 42 | private: 43 | QMediaPlayer *player; 44 | QAudioOutput *m_output; 45 | QUrl url; 46 | PowerManagementInterface *m_power; 47 | int amountBuffers = 0; 48 | qreal volume = 1.0; 49 | 50 | Q_SIGNALS: 51 | void durationChanged(); 52 | void urlChanged(); 53 | void volumeChanged(); 54 | void posChanged(); 55 | void stateChanged(); 56 | void playingChanged(); 57 | void finished(); 58 | }; 59 | -------------------------------------------------------------------------------- /src/services/local/playlist.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | class TracksModel; 8 | class Player; 9 | class Playlist : public QObject, public QQmlParserStatus 10 | { 11 | Q_INTERFACES(QQmlParserStatus) 12 | Q_OBJECT 13 | 14 | Q_PROPERTY(TracksModel *model WRITE setModel READ model NOTIFY modelChanged) 15 | Q_PROPERTY(QVariantMap currentTrack READ currentTrack NOTIFY currentTrackChanged FINAL) 16 | Q_PROPERTY(int currentIndex READ currentIndex NOTIFY currentIndexChanged FINAL) 17 | 18 | Q_PROPERTY(PlayMode playMode READ playMode WRITE setPlayMode NOTIFY playModeChanged) 19 | 20 | Q_PROPERTY(RepeatMode repeatMode READ repeatMode WRITE setRepeatMode NOTIFY repeatModeChanged) 21 | 22 | Q_PROPERTY(bool autoResume READ autoResume WRITE setAutoResume NOTIFY autoResumeChanged) 23 | 24 | public: 25 | enum PlayMode : uint_fast8_t 26 | { 27 | Normal, 28 | Shuffle, 29 | }; 30 | Q_ENUM(PlayMode) 31 | 32 | enum RepeatMode : uint_fast8_t 33 | { 34 | NoRepeat, 35 | RepeatOnce, 36 | Repeat 37 | }; 38 | Q_ENUM(RepeatMode) 39 | 40 | explicit Playlist(QObject *parent = nullptr); 41 | TracksModel *model() const; 42 | 43 | QVariantMap currentTrack() const; 44 | 45 | int currentIndex() const; 46 | PlayMode playMode() const; 47 | 48 | RepeatMode repeatMode() const; 49 | 50 | bool autoResume() const; 51 | 52 | void classBegin() override final; 53 | void componentComplete() override final; 54 | 55 | private: 56 | TracksModel *m_model = nullptr; 57 | Player *m_player = nullptr; 58 | QVariantMap m_currentTrack; 59 | int m_currentIndex = -1; 60 | 61 | PlayMode m_playMode = PlayMode::Normal; 62 | RepeatMode m_repeatMode = RepeatMode::NoRepeat; 63 | uint m_repeatFlag = 0; 64 | 65 | bool m_autoResume; 66 | 67 | public Q_SLOTS: 68 | bool canGoNext() const; 69 | bool canGoPrevious() const; 70 | bool canPlay() const; 71 | 72 | void next(); 73 | void previous(); 74 | void play(int index); 75 | void clear(); 76 | 77 | void save(); 78 | void loadLastPlaylist(); 79 | 80 | void append(const QVariantMap &track); 81 | void insert(const QStringList &urls, const int &index); 82 | 83 | void setModel(TracksModel *model); 84 | void setCurrentIndex(int index); 85 | void changeCurrentIndex(int index); 86 | 87 | void setPlayMode(Playlist::PlayMode playMode); 88 | 89 | void shuffleRange(int start, int stop); 90 | 91 | void move(int from, int to); 92 | void remove(int index); 93 | 94 | void setRepeatMode(RepeatMode repeatMode); 95 | 96 | void setAutoResume(bool autoResume); 97 | 98 | Q_SIGNALS: 99 | void canPlayChanged(); 100 | void modelChanged(TracksModel *model); 101 | void currentTrackChanged(QVariantMap currentTrack); 102 | void currentIndexChanged(int currentIndex); 103 | void missingFile(QVariantMap track); 104 | void playModeChanged(Playlist::PlayMode playMode); 105 | void repeatModeChanged(RepeatMode repeatMode); 106 | void autoResumeChanged(bool autoResume); 107 | }; 108 | -------------------------------------------------------------------------------- /src/services/local/powermanagementinterface.h: -------------------------------------------------------------------------------- 1 | /* 2 | SPDX-FileCopyrightText: 2019 (c) Matthieu Gallien 3 | 4 | SPDX-License-Identifier: LGPL-3.0-or-later 5 | */ 6 | 7 | #pragma once 8 | 9 | #include 10 | 11 | #include 12 | 13 | class QDBusPendingCallWatcher; 14 | class PowerManagementInterfacePrivate; 15 | 16 | class PowerManagementInterface : public QObject 17 | { 18 | 19 | Q_OBJECT 20 | 21 | Q_PROPERTY(bool preventSleep 22 | READ preventSleep 23 | WRITE setPreventSleep 24 | NOTIFY preventSleepChanged) 25 | 26 | Q_PROPERTY(bool sleepInhibited 27 | READ sleepInhibited 28 | NOTIFY sleepInhibitedChanged) 29 | 30 | public: 31 | 32 | explicit PowerManagementInterface(QObject *parent = nullptr); 33 | 34 | ~PowerManagementInterface() override; 35 | 36 | [[nodiscard]] bool preventSleep() const; 37 | 38 | [[nodiscard]] bool sleepInhibited() const; 39 | 40 | Q_SIGNALS: 41 | 42 | void preventSleepChanged(); 43 | 44 | void sleepInhibitedChanged(); 45 | 46 | public Q_SLOTS: 47 | 48 | void setPreventSleep(bool value); 49 | 50 | void retryInhibitingSleep(); 51 | 52 | private Q_SLOTS: 53 | 54 | void hostSleepInhibitChanged(); 55 | 56 | void inhibitDBusCallFinishedPlasmaWorkspace(QDBusPendingCallWatcher *aWatcher); 57 | 58 | void uninhibitDBusCallFinishedPlasmaWorkspace(QDBusPendingCallWatcher *aWatcher); 59 | 60 | void inhibitDBusCallFinishedGnomeWorkspace(QDBusPendingCallWatcher *aWatcher); 61 | 62 | void uninhibitDBusCallFinishedGnomeWorkspace(QDBusPendingCallWatcher *aWatcher); 63 | 64 | private: 65 | 66 | void inhibitSleepPlasmaWorkspace(); 67 | 68 | void uninhibitSleepPlasmaWorkspace(); 69 | 70 | void inhibitSleepGnomeWorkspace(); 71 | 72 | void uninhibitSleepGnomeWorkspace(); 73 | 74 | void inhibitSleepWindowsWorkspace(); 75 | 76 | void uninhibitSleepWindowsWorkspace(); 77 | 78 | std::unique_ptr d; 79 | 80 | }; 81 | -------------------------------------------------------------------------------- /src/services/local/taginfo.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Babe - tiny music player 3 | Copyright (C) 2017 Camilo Higuita 4 | This program is free software; you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation; either version 3 of the License, or 7 | (at your option) any later version. 8 | This program is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | GNU General Public License for more details. 12 | You should have received a copy of the GNU General Public License 13 | along with this program; if not, write to the Free Software Foundation, 14 | Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 15 | 16 | */ 17 | 18 | #include "taginfo.h" 19 | #include "../../utils/bae.h" 20 | 21 | #include 22 | #include 23 | 24 | using namespace BAE; 25 | 26 | TagInfo::TagInfo(const QString &url, QObject *parent) 27 | : QObject(parent) 28 | { 29 | this->setFile(url); 30 | } 31 | 32 | TagInfo::TagInfo(QObject *parent) 33 | : QObject(parent) 34 | {} 35 | 36 | TagInfo::~TagInfo() 37 | { 38 | delete this->file; 39 | } 40 | 41 | bool TagInfo::isNull() 42 | { 43 | return this->file->isNull(); 44 | } 45 | 46 | QString TagInfo::getAlbum() const 47 | { 48 | const auto value = QString::fromStdWString(file->tag()->album().toWString()); 49 | return !value.isEmpty() ? value : SLANG[W::UNKNOWN]; 50 | } 51 | 52 | QString TagInfo::getTitle() const 53 | { 54 | const auto value = QString::fromStdWString(file->tag()->title().toWString()); 55 | return !value.isEmpty() ? value : fileName(); 56 | } 57 | 58 | QString TagInfo::getArtist() const 59 | { 60 | const auto value = QString::fromStdWString(file->tag()->artist().toWString()); 61 | return !value.isEmpty() ? value : SLANG[W::UNKNOWN]; 62 | } 63 | 64 | int TagInfo::getTrack() const 65 | { 66 | return static_cast(file->tag()->track()); 67 | } 68 | 69 | QString TagInfo::getGenre() const 70 | { 71 | const auto value = QString::fromStdWString(file->tag()->genre().toWString()); 72 | return !value.isEmpty() ? value : SLANG[W::UNKNOWN]; 73 | } 74 | 75 | QString TagInfo::fileName() const 76 | { 77 | return QFileInfo(path).fileName(); 78 | } 79 | 80 | uint TagInfo::getYear() const 81 | { 82 | return file->tag()->year(); 83 | } 84 | 85 | void TagInfo::setFile(const QString &url) 86 | { 87 | this->path = url; 88 | QFileInfo _file(this->path); 89 | 90 | if (_file.isReadable() && _file.exists()) { 91 | this->file = new TagLib::FileRef(TagLib::FileName(path.toUtf8())); 92 | } else 93 | this->file = new TagLib::FileRef(); 94 | } 95 | 96 | int TagInfo::getDuration() const 97 | { 98 | return file->audioProperties()->length(); 99 | } 100 | 101 | QString TagInfo::getComment() const 102 | { 103 | const auto value = QString::fromStdWString(file->tag()->comment().toWString()); 104 | return !value.isEmpty() ? value : SLANG[W::UNKNOWN]; 105 | } 106 | 107 | QByteArray TagInfo::getCover() const 108 | { 109 | QByteArray array; 110 | return array; 111 | } 112 | 113 | void TagInfo::setCover(const QByteArray &array) 114 | { 115 | Q_UNUSED(array); 116 | } 117 | 118 | void TagInfo::setComment(const QString &comment) 119 | { 120 | this->file->tag()->setComment(comment.toStdString()); 121 | this->file->save(); 122 | } 123 | 124 | void TagInfo::setAlbum(const QString &album) 125 | { 126 | this->file->tag()->setAlbum(album.toStdString()); 127 | this->file->save(); 128 | } 129 | 130 | void TagInfo::setTitle(const QString &title) 131 | { 132 | this->file->tag()->setTitle(title.toStdString()); 133 | this->file->save(); 134 | } 135 | 136 | void TagInfo::setTrack(const int &track) 137 | { 138 | this->file->tag()->setTrack(static_cast(track)); 139 | this->file->save(); 140 | } 141 | 142 | void TagInfo::setYear(const int &year) 143 | { 144 | this->file->tag()->setYear(static_cast(year)); 145 | this->file->save(); 146 | } 147 | 148 | void TagInfo::setArtist(const QString &artist) 149 | { 150 | this->file->tag()->setArtist(artist.toStdString()); 151 | this->file->save(); 152 | } 153 | 154 | void TagInfo::setGenre(const QString &genre) 155 | { 156 | this->file->tag()->setGenre(genre.toStdString()); 157 | this->file->save(); 158 | } 159 | -------------------------------------------------------------------------------- /src/services/local/taginfo.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace TagLib 8 | { 9 | class FileRef; 10 | } 11 | class TagInfo : public QObject 12 | { 13 | Q_OBJECT 14 | public: 15 | TagInfo(const QString &url, QObject *parent = nullptr); 16 | TagInfo(QObject *parent = nullptr); 17 | 18 | ~TagInfo(); 19 | bool isNull(); 20 | QString getAlbum() const; 21 | QString getTitle() const; 22 | QString getArtist() const; 23 | int getTrack() const; 24 | QString getGenre() const; 25 | QString fileName() const; 26 | QString getComment() const; 27 | QByteArray getCover() const; 28 | int getDuration() const; 29 | uint getYear() const; 30 | 31 | void setFile(const QString &url); 32 | void setAlbum(const QString &album); 33 | void setTitle(const QString &title); 34 | void setTrack(const int &track); 35 | void setYear(const int &year); 36 | void setArtist(const QString &artist); 37 | void setGenre(const QString &genre); 38 | void setComment(const QString &comment); 39 | void setCover(const QByteArray &array); 40 | 41 | private: 42 | TagLib::FileRef *file; 43 | QString path; 44 | wchar_t *m_path; 45 | }; 46 | -------------------------------------------------------------------------------- /src/services/local/trackinfo.cpp: -------------------------------------------------------------------------------- 1 | #include "trackinfo.h" 2 | #include "pulpo/pulpo.h" 3 | 4 | 5 | TrackInfo::TrackInfo(QObject *parent) : QObject(parent) 6 | { 7 | connect(this, &TrackInfo::trackChanged, this, &TrackInfo::getInfo); 8 | } 9 | 10 | QString TrackInfo::albumWiki() const 11 | { 12 | return m_albumWiki; 13 | } 14 | 15 | QString TrackInfo::artistWiki() const 16 | { 17 | return m_artistWiki; 18 | } 19 | 20 | QString TrackInfo::trackWiki() const 21 | { 22 | return m_trackWiki; 23 | } 24 | 25 | QString TrackInfo::lyrics() const 26 | { 27 | return m_lyrics; 28 | } 29 | 30 | QVariantMap TrackInfo::track() const 31 | { 32 | return m_track; 33 | } 34 | 35 | void TrackInfo::setTrack(QVariantMap track) 36 | { 37 | if (m_track == track) 38 | return; 39 | 40 | m_track = track; 41 | Q_EMIT trackChanged(m_track); 42 | } 43 | 44 | void TrackInfo::getInfo() 45 | { 46 | if(m_track.isEmpty()) 47 | return; 48 | 49 | auto artist = m_track.value("artist").toString(); 50 | auto album = m_track.value("album").toString(); 51 | auto title = m_track.value("title").toString(); 52 | 53 | if(artist!= m_artist) 54 | { 55 | m_artist = artist; 56 | getArtistInfo(); 57 | } 58 | 59 | if(album != m_album) 60 | { 61 | m_album = album; 62 | getAlbumInfo(); 63 | } 64 | 65 | if(title != m_title) 66 | { 67 | m_title = title; 68 | getTrackInfo(); 69 | } 70 | 71 | } 72 | 73 | void TrackInfo::getAlbumInfo() 74 | { 75 | PULPO::REQUEST request; 76 | request.track = FMH::toModel(m_track); 77 | request.ontology = PULPO::ONTOLOGY::ALBUM; 78 | request.services = {PULPO::SERVICES::LastFm, PULPO::SERVICES::Spotify}; 79 | request.info = {PULPO::INFO::WIKI}; 80 | request.callback = [&](PULPO::REQUEST request, PULPO::RESPONSES responses) { 81 | qDebug() << "DONE WITH " << request.track; 82 | 83 | for (const auto &res : responses) { 84 | if (res.context == PULPO::PULPO_CONTEXT::WIKI) { 85 | m_albumWiki = res.value.toString(); 86 | Q_EMIT this->albumWikiChanged(m_albumWiki); 87 | } 88 | } 89 | }; 90 | 91 | auto pulpo = new Pulpo; 92 | QObject::connect(pulpo, &Pulpo::finished, pulpo, &Pulpo::deleteLater); 93 | QObject::connect(pulpo, &Pulpo::error, [pulpo]() { 94 | pulpo->deleteLater(); 95 | }); 96 | 97 | pulpo->request(request); 98 | } 99 | 100 | void TrackInfo::getArtistInfo() 101 | { 102 | PULPO::REQUEST request; 103 | request.track = FMH::toModel(m_track); 104 | request.ontology = PULPO::ONTOLOGY::ARTIST; 105 | request.services = {PULPO::SERVICES::LastFm, PULPO::SERVICES::Spotify}; 106 | request.info = {PULPO::INFO::WIKI}; 107 | request.callback = [&](PULPO::REQUEST request, PULPO::RESPONSES responses) { 108 | qDebug() << "DONE WITH " << request.track; 109 | 110 | for (const auto &res : responses) { 111 | if (res.context == PULPO::PULPO_CONTEXT::WIKI) { 112 | m_artistWiki = res.value.toString(); 113 | Q_EMIT this->artistWikiChanged(m_artistWiki); 114 | } 115 | } 116 | }; 117 | 118 | auto pulpo = new Pulpo; 119 | QObject::connect(pulpo, &Pulpo::finished, pulpo, &Pulpo::deleteLater); 120 | QObject::connect(pulpo, &Pulpo::error, [pulpo]() { 121 | pulpo->deleteLater(); 122 | }); 123 | 124 | pulpo->request(request); 125 | } 126 | 127 | void TrackInfo::getTrackInfo() 128 | { 129 | PULPO::REQUEST request; 130 | request.track = FMH::toModel(m_track); 131 | request.ontology = PULPO::ONTOLOGY::TRACK; 132 | request.services = {PULPO::SERVICES::LyricWikia, PULPO::SERVICES::WikiLyrics}; 133 | request.info = {PULPO::INFO::LYRICS}; 134 | request.callback = [&](PULPO::REQUEST request, PULPO::RESPONSES responses) { 135 | qDebug() << "DONE WITH " << request.track; 136 | 137 | for (const auto &res : responses) { 138 | if (res.context == PULPO::PULPO_CONTEXT::LYRIC) { 139 | m_lyrics = res.value.toString(); 140 | Q_EMIT this->lyricsChanged(m_lyrics); 141 | } 142 | } 143 | }; 144 | 145 | auto pulpo = new Pulpo; 146 | QObject::connect(pulpo, &Pulpo::finished, pulpo, &Pulpo::deleteLater); 147 | QObject::connect(pulpo, &Pulpo::error, [pulpo]() { 148 | pulpo->deleteLater(); 149 | }); 150 | 151 | pulpo->request(request); 152 | } 153 | -------------------------------------------------------------------------------- /src/services/local/trackinfo.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | class TrackInfo : public QObject 8 | { 9 | Q_OBJECT 10 | Q_PROPERTY(QString albumWiki READ albumWiki NOTIFY albumWikiChanged) 11 | Q_PROPERTY(QString artistWiki READ artistWiki NOTIFY artistWikiChanged) 12 | Q_PROPERTY(QString trackWiki READ trackWiki NOTIFY trackWikiChanged) 13 | Q_PROPERTY(QString lyrics READ lyrics NOTIFY lyricsChanged) 14 | 15 | Q_PROPERTY(QVariantMap track READ track WRITE setTrack NOTIFY trackChanged) 16 | 17 | public: 18 | explicit TrackInfo(QObject *parent = nullptr); 19 | 20 | QString albumWiki() const; 21 | 22 | QString artistWiki() const; 23 | 24 | QString trackWiki() const; 25 | 26 | QString lyrics() const; 27 | 28 | QVariantMap track() const; 29 | 30 | public Q_SLOTS: 31 | void setTrack(QVariantMap track); 32 | 33 | private: 34 | QString m_albumWiki; 35 | 36 | QString m_artistWiki; 37 | 38 | QString m_trackWiki; 39 | 40 | QString m_lyrics; 41 | 42 | QVariantMap m_track; 43 | 44 | void getInfo(); 45 | 46 | void getAlbumInfo(); 47 | void getArtistInfo(); 48 | void getTrackInfo(); 49 | 50 | QString m_artist; 51 | QString m_album; 52 | QString m_title; 53 | 54 | Q_SIGNALS: 55 | void albumWikiChanged(QString albumWiki); 56 | void artistWikiChanged(QString artistWiki); 57 | void trackWikiChanged(QString trackWiki); 58 | void lyricsChanged(QString lyrics); 59 | void trackChanged(QVariantMap track); 60 | }; 61 | -------------------------------------------------------------------------------- /src/services/web/NextCloud/nextmusic.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "abstractmusicprovider.h" 4 | #include 5 | 6 | class NextMusic : public AbstractMusicProvider 7 | { 8 | Q_OBJECT 9 | public: 10 | explicit NextMusic(QObject *parent = nullptr); 11 | QVariantList getAlbumsList() const override final; 12 | QVariantList getArtistsList() const override final; 13 | 14 | FMH::MODEL getTrackItem(const QString &id); 15 | void getTrackPath(const QString &id); 16 | 17 | void getCollection(const std::initializer_list ¶meters = {}) override final; 18 | void getTracks() override final; 19 | void getTrack(const QString &id) override final; 20 | void getArtists() override final; 21 | void getArtist(const QString &id) override final; 22 | void getAlbums() override final; 23 | void getAlbum(const QString &id) override final; 24 | void getPlaylists() override final; 25 | void getPlaylist(const QString &id) override final; 26 | void getFolders() override final; 27 | void getFolder(const QString &id) override final; 28 | 29 | private: 30 | const static QString API; 31 | static const QString formatUrl(const QString &user, const QString &password, const QString &provider); 32 | 33 | FMH::MODEL_LIST parseCollection(const QByteArray &array); 34 | 35 | QVariantList m_artists; 36 | QVariantList m_albums; 37 | QHash m_tracks; //(id: trackMap) 38 | }; 39 | -------------------------------------------------------------------------------- /src/services/web/abstractmusicprovider.cpp: -------------------------------------------------------------------------------- 1 | #include "abstractmusicprovider.h" 2 | 3 | AbstractMusicProvider::AbstractMusicProvider(QObject *parent) 4 | : QObject(parent) 5 | { 6 | } 7 | -------------------------------------------------------------------------------- /src/services/web/abstractmusicprovider.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | 7 | /** 8 | * @brief The AbstractMusicSyncer class 9 | * is an abstraction for different services backend to stream music. 10 | * Different services to be added to VVave are expected to derived from this. 11 | */ 12 | 13 | class AbstractMusicProvider : public QObject 14 | { 15 | Q_OBJECT 16 | public: 17 | explicit AbstractMusicProvider(QObject *parent = nullptr); 18 | virtual ~AbstractMusicProvider() 19 | { 20 | } 21 | 22 | virtual void getCollection(const std::initializer_list ¶meters = {}) = 0; 23 | 24 | virtual void getTracks() = 0; 25 | virtual void getTrack(const QString &id) = 0; 26 | 27 | virtual void getArtists() = 0; 28 | virtual void getArtist(const QString &id) = 0; 29 | 30 | virtual void getAlbums() = 0; 31 | virtual void getAlbum(const QString &id) = 0; 32 | 33 | virtual void getPlaylists() = 0; 34 | virtual void getPlaylist(const QString &id) = 0; 35 | 36 | virtual void getFolders() = 0; 37 | virtual void getFolder(const QString &id) = 0; 38 | 39 | virtual QVariantList getAlbumsList() const 40 | { 41 | return QVariantList(); 42 | } 43 | virtual QVariantList getArtistsList() const 44 | { 45 | return QVariantList(); 46 | } 47 | /** 48 | * @brief setCredentials 49 | * sets the credential to authenticate to the provider server 50 | * @param account 51 | * the account data is represented by FMH::MODEL 52 | */ 53 | virtual void setCredentials(const FMH::MODEL &account) final 54 | { 55 | this->m_user = account[FMH::MODEL_KEY::USER]; 56 | this->m_password = account[FMH::MODEL_KEY::PASSWORD]; 57 | this->m_provider = account[FMH::MODEL_KEY::SERVER]; 58 | } 59 | 60 | virtual QString user() final 61 | { 62 | return this->m_user; 63 | } 64 | virtual QString provider() final 65 | { 66 | return this->m_provider; 67 | } 68 | 69 | protected: 70 | QString m_user = ""; 71 | QString m_password = ""; 72 | QString m_provider = ""; 73 | 74 | Q_SIGNALS: 75 | void collectionReady(FMH::MODEL_LIST data); 76 | void tracksReady(FMH::MODEL_LIST data); 77 | void trackReady(FMH::MODEL data); 78 | void artistsRedy(FMH::MODEL_LIST data); 79 | void artistReady(FMH::MODEL data); 80 | void albumsReady(FMH::MODEL_LIST data); 81 | void albumReady(FMH::MODEL data); 82 | void playlistsReady(FMH::MODEL_LIST data); 83 | void playlistReady(FMH::MODEL data); 84 | 85 | void trackPathReady(QString id, QString path); 86 | }; 87 | -------------------------------------------------------------------------------- /src/utils/Player.js: -------------------------------------------------------------------------------- 1 | .import org.mauikit.filebrowsing as FB 2 | 3 | .import org.maui.vvave as Vvave 4 | 5 | function playTrack() 6 | { 7 | player.url = currentTrack.url ? currentTrack.url : ""; 8 | player.play() 9 | } 10 | 11 | function queueTracks(tracks) 12 | { 13 | if(tracks && tracks.length > 0) 14 | { 15 | appendTracksAt(tracks, currentTrackIndex+1) 16 | // root.notify("", "Queue", tracks.length + " tracks added put on queue") 17 | // This freezes the whole UI. It should probably be a toast popup instead. 18 | // Something similar AbstractApplicationWindow::showPassiveNotification() from Kirigami would be less flow-breaking. 19 | } 20 | } 21 | 22 | function setLyrics(lyrics) 23 | { 24 | currentTrack.lyrics = lyrics 25 | infoView.lyricsText.text = lyrics 26 | } 27 | 28 | function stop() 29 | { 30 | player.stop() 31 | } 32 | 33 | function changeCurrentIndex(index) 34 | { 35 | root.playlistManager.changeCurrentIndex(index) 36 | } 37 | 38 | function nextTrack() 39 | { 40 | root.playlistManager.next() 41 | } 42 | 43 | function previousTrack() 44 | { 45 | root.playlistManager.previous() 46 | } 47 | 48 | function playAt(index) 49 | { 50 | root.playlistManager.play(index) 51 | } 52 | 53 | function quickPlay(track) 54 | { 55 | // root.pageStack.currentIndex = 0 56 | appendTrack(track) 57 | playAt(mainPlaylist.listView.count-1) 58 | mainPlaylist.listView.positionViewAtEnd() 59 | } 60 | 61 | function appendTracksAt(tracks, at) 62 | { 63 | for(var i in tracks) 64 | { 65 | mainPlaylist.listModel.list.appendAt(tracks[i], parseInt(at)+parseInt(i)) 66 | } 67 | } 68 | 69 | function appendUrls(urls) 70 | { 71 | mainPlaylist.listModel.list.appendUrls(urls) 72 | } 73 | 74 | function appendUrlsAt(urls, at) 75 | { 76 | mainPlaylist.listModel.list.insertUrls(urls, at) 77 | } 78 | 79 | function appendTrack(track) 80 | { 81 | if(track) 82 | { 83 | root.playlistManager.append(track) 84 | if(sync === true) 85 | { 86 | FB.Tagging.tagUrl(track.url, syncPlaylist) 87 | } 88 | } 89 | } 90 | 91 | function addTrack(track) 92 | { 93 | if(track) 94 | { 95 | appendTrack(track) 96 | mainPlaylist.listView.positionViewAtEnd() 97 | } 98 | } 99 | 100 | function appendAll(tracks) 101 | { 102 | for(var track of tracks) 103 | appendTrack(track) 104 | 105 | mainPlaylist.listView.positionViewAtEnd() 106 | 107 | if (tracks.length > 1 && root.playlistManager.playMode === Vvave.Playlist.Shuffle) 108 | { 109 | root.playlistManager.shuffleRange( 110 | mainPlaylist.listView.count - tracks.length, 111 | mainPlaylist.listView.count) 112 | } 113 | } 114 | 115 | function playAll(tracks) 116 | { 117 | sync = false 118 | syncPlaylist = "" 119 | 120 | root.playlistManager.clear() 121 | appendAll(tracks) 122 | 123 | if(_drawer.modal && !_drawer.visible) 124 | _drawer.visible = true 125 | 126 | mainPlaylist.listView.positionViewAtBeginning() 127 | playAt(0) 128 | } 129 | 130 | function appendAllModel(model) 131 | { 132 | mainPlaylist.listModel.list.copy(model) 133 | mainPlaylist.listView.positionViewAtEnd() 134 | 135 | if (model.count > 1 && root.playlistManager.playMode === Vvave.Playlist.Shuffle) 136 | { 137 | root.playlistManager.shuffleRange( 138 | mainPlaylist.listView.count - model.count, 139 | mainPlaylist.listView.count) 140 | } 141 | } 142 | 143 | function playQuery(query) 144 | { 145 | const tracks = Vvave.Vvave.getTracks(query) 146 | playAll(tracks) 147 | } 148 | 149 | function playAllModel(model) 150 | { 151 | sync = false 152 | syncPlaylist = "" 153 | 154 | root.playlistManager.clear() 155 | appendAllModel(model) 156 | 157 | if(_drawer.modal && !_drawer.visible) 158 | _drawer.visible = true 159 | 160 | mainPlaylist.listView.positionViewAtBeginning() 161 | playAt(0) 162 | } 163 | 164 | function shuffleAllModel(model) 165 | { 166 | sync = false 167 | syncPlaylist = "" 168 | 169 | root.playlistManager.clear() 170 | appendAllModel(model) 171 | 172 | if(_drawer.modal && !_drawer.visible) 173 | _drawer.visible = true 174 | 175 | mainPlaylist.listView.positionViewAtBeginning() 176 | root.playlistManager.playMode = Vvave.Playlist.Shuffle 177 | playAt(0) 178 | } 179 | -------------------------------------------------------------------------------- /src/utils/bae.h: -------------------------------------------------------------------------------- 1 | #ifndef BAE_H 2 | #define BAE_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | namespace BAE 13 | { 14 | enum class W : uint_fast8_t { ALL, NONE, LIKE, TAG, SIMILAR, UNKNOWN, DONE, DESC, ASC, CODE, MSG }; 15 | 16 | static const QMap SLANG = 17 | {{W::ALL, "ALL"}, {W::NONE, "NONE"}, {W::LIKE, "LIKE"}, {W::SIMILAR, "SIMILAR"}, {W::UNKNOWN, "UNKNOWN"}, {W::DONE, "DONE"}, {W::DESC, "DESC"}, {W::ASC, "ASC"}, {W::TAG, "TAG"}, {W::MSG, "MSG"}, {W::CODE, "CODE"}}; 18 | 19 | enum class TABLE : uint8_t { ALBUMS, ARTISTS, SOURCES, SOURCES_TYPES, TRACKS, FOLDERS, ALL, NONE }; 20 | 21 | static const QMap TABLEMAP = {{TABLE::ALBUMS, "albums"}, {TABLE::ARTISTS, "artists"}, {TABLE::SOURCES, "sources"}, {TABLE::SOURCES_TYPES, "sources_types"}, {TABLE::TRACKS, "tracks"}, {TABLE::FOLDERS, "folders"} 22 | 23 | }; 24 | 25 | enum class KEY : uint8_t { 26 | URL = 0, 27 | SOURCES_URL = 1, 28 | TRACK = 2, 29 | TITLE = 3, 30 | ARTIST = 4, 31 | ALBUM = 5, 32 | DURATION = 6, 33 | PLAYED = 7, 34 | STARS = 9, 35 | RELEASE_DATE = 10, 36 | ADD_DATE = 11, 37 | LYRICS = 12, 38 | GENRE = 13, 39 | ART = 14, 40 | TAG = 15, 41 | MOOD = 16, 42 | PLAYLIST = 17, 43 | ARTWORK = 18, 44 | WIKI = 19, 45 | SOURCE_TYPE = 20, 46 | CONTEXT = 21, 47 | RETRIEVAL_DATE = 22, 48 | COMMENT = 23, 49 | ID = 24, 50 | SQL = 25, 51 | NONE = 26 52 | }; 53 | 54 | typedef QMap DB; 55 | typedef QList DB_LIST; 56 | 57 | static const DB KEYMAP = {{KEY::URL, "url"}, 58 | {KEY::SOURCES_URL, "sources_url"}, 59 | {KEY::TRACK, "track"}, 60 | {KEY::TITLE, "title"}, 61 | {KEY::ARTIST, "artist"}, 62 | {KEY::ALBUM, "album"}, 63 | {KEY::DURATION, "duration"}, 64 | {KEY::PLAYED, "played"}, 65 | {KEY::STARS, "stars"}, 66 | {KEY::RELEASE_DATE, "releaseDate"}, 67 | {KEY::ADD_DATE, "addDate"}, 68 | {KEY::LYRICS, "lyrics"}, 69 | {KEY::GENRE, "genre"}, 70 | {KEY::ART, "art"}, 71 | {KEY::TAG, "tag"}, 72 | {KEY::MOOD, "mood"}, 73 | {KEY::PLAYLIST, "playlist"}, 74 | {KEY::ARTWORK, "artwork"}, 75 | {KEY::WIKI, "wiki"}, 76 | {KEY::SOURCE_TYPE, "source_types_id"}, 77 | {KEY::CONTEXT, "context"}, 78 | {KEY::RETRIEVAL_DATE, "retrieval_date"}, 79 | {KEY::ID, "id"}, 80 | {KEY::COMMENT, "comment"}, 81 | {KEY::SQL, "sql"}}; 82 | 83 | 84 | #ifdef Q_OS_ANDROID 85 | static const QString CollectionDBPath = QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::HomeLocation) + "/vvave/").toLocalFile(); 86 | const static QUrl CachePath = QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::HomeLocation) + "/vvave/"); 87 | #else 88 | static const QString CollectionDBPath = QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/vvave/").toLocalFile(); 89 | const static QUrl CachePath = QUrl::fromLocalFile(QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + "/vvave/"); 90 | #endif 91 | 92 | const static QString DBName = QStringLiteral("collection_v2.db"); 93 | const static QStringList defaultSources = QStringList() << FMStatic::MusicPath << FMStatic::DownloadsPath; 94 | 95 | static inline BAE::TABLE albumType(const FMH::MODEL &albumMap) 96 | { 97 | if (albumMap[FMH::MODEL_KEY::ALBUM].isEmpty() && !albumMap[FMH::MODEL_KEY::ARTIST].isEmpty()) 98 | return BAE::TABLE::ARTISTS; 99 | else if (!albumMap[FMH::MODEL_KEY::ALBUM].isEmpty() && !albumMap[FMH::MODEL_KEY::ARTIST].isEmpty()) 100 | return BAE::TABLE::ALBUMS; 101 | 102 | return BAE::TABLE::NONE; 103 | } 104 | 105 | static inline void fixArtworkImageFileName(QString &title) 106 | { 107 | title.replace("/", "_"); 108 | title.replace(".", "_"); 109 | title.replace("+", "_"); 110 | title.replace("&", "_"); 111 | } 112 | 113 | static inline bool artworkCache(FMH::MODEL &track, const FMH::MODEL_KEY &type = FMH::MODEL_KEY::ID) 114 | { 115 | QDirIterator it(CachePath.toLocalFile(), QDir::Files, QDirIterator::NoIteratorFlags); 116 | while (it.hasNext()) { 117 | const auto file = QUrl::fromLocalFile(it.next()); 118 | const auto fileName = QFileInfo(file.toLocalFile()).baseName(); 119 | switch (type) { 120 | case FMH::MODEL_KEY::ALBUM: { 121 | QString name = track[FMH::MODEL_KEY::ARTIST] + "_" + track[FMH::MODEL_KEY::ALBUM]; 122 | fixArtworkImageFileName(name); 123 | 124 | if (fileName == name) { 125 | track.insert(FMH::MODEL_KEY::ARTWORK, file.toString()); 126 | return true; 127 | } else 128 | continue; 129 | break; 130 | } 131 | 132 | case FMH::MODEL_KEY::ARTIST: { 133 | auto name = track[FMH::MODEL_KEY::ARTIST]; 134 | fixArtworkImageFileName(name); 135 | 136 | if (fileName == name) { 137 | track.insert(FMH::MODEL_KEY::ARTWORK, file.toString()); 138 | return true; 139 | } else 140 | continue; 141 | break; 142 | } 143 | default: 144 | break; 145 | } 146 | } 147 | return false; 148 | } 149 | 150 | } 151 | 152 | #endif // BAE_H 153 | -------------------------------------------------------------------------------- /src/vvave.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | class vvave : public QObject 9 | { 10 | Q_OBJECT 11 | Q_PROPERTY(QVariantList sources READ sourcesModel NOTIFY sourcesChanged FINAL) 12 | Q_PROPERTY(QList folders READ folders NOTIFY sourcesChanged FINAL) 13 | Q_PROPERTY(bool fetchArtwork READ fetchArtwork WRITE setFetchArtwork NOTIFY fetchArtworkChanged) 14 | Q_PROPERTY(bool scanning READ scanning NOTIFY scanningChanged FINAL) 15 | 16 | public: 17 | explicit vvave(QObject *parent = nullptr); 18 | static vvave *instance(); 19 | 20 | bool fetchArtwork() const; 21 | 22 | QList folders(); 23 | 24 | bool scanning() const; 25 | 26 | public Q_SLOTS: 27 | void addSources(const QList &paths); 28 | bool removeSource(const QString &source); 29 | 30 | void scanDir(const QList &paths); 31 | void rescan(); 32 | 33 | static QStringList sources(); 34 | static QVariantList sourcesModel(); 35 | 36 | void setFetchArtwork(bool fetchArtwork); 37 | static FMH::MODEL trackInfo(const QUrl &url); 38 | 39 | QString artworkUrl(const QString &artist, const QString &album); 40 | 41 | /** 42 | * @brief Get the tracks resulting from looking up the DB with a given query 43 | * @param query The querystring 44 | * @return A list of tracks 45 | */ 46 | QVariantList getTracks(const QString &query); 47 | 48 | private: 49 | bool m_fetchArtwork = false; 50 | bool m_scanning = false; 51 | 52 | Q_SIGNALS: 53 | void sourceAdded(QUrl source); 54 | void sourceRemoved(QUrl source); 55 | 56 | void sourcesChanged(); 57 | void fetchArtworkChanged(bool fetchArtwork); 58 | void scanningChanged(bool scanning); 59 | }; 60 | -------------------------------------------------------------------------------- /src/widgets/AlbumsView.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2 | import QtQuick.Controls 3 | 4 | import org.mauikit.controls as Maui 5 | 6 | import "BabeGrid" 7 | import "BabeTable" 8 | 9 | import "../db/Queries.js" as Q 10 | import "../utils/Player.js" as Player 11 | 12 | StackView 13 | { 14 | id: control 15 | 16 | property alias list : albumsViewGrid.list 17 | 18 | property string currentQuery: "" 19 | property string currentAlbum: "" 20 | property string currentArtist: "" 21 | 22 | property alias holder: albumsViewGrid.holder 23 | property alias prefix: albumsViewGrid.prefix 24 | 25 | property Flickable flickable : currentItem.flickable 26 | 27 | initialItem: BabeGrid 28 | { 29 | id: albumsViewGrid 30 | holder.emoji: "qrc:/assets/dialog-information.svg" 31 | holder.actions:[ 32 | 33 | Action 34 | { 35 | text: i18n("Add sources") 36 | onTriggered: openSettingsDialog() 37 | } 38 | ] 39 | 40 | onAlbumCoverClicked:(album, artist) => control.populateTable(album, artist) 41 | onPlayAll: (album, artist) => 42 | { 43 | var query 44 | if(album && artist) 45 | { 46 | query = Q.GET.albumTracks_.arg(album) 47 | query = query.arg(artist) 48 | }else if(artist && !album) 49 | { 50 | query = Q.GET.artistTracks_.arg(artist) 51 | } 52 | 53 | Player.playQuery(query) 54 | } 55 | } 56 | 57 | Component 58 | { 59 | id: _tracksTableComponent 60 | 61 | BabeTable 62 | { 63 | list.query: control.currentQuery 64 | trackNumberVisible: true 65 | coverArtVisible: settings.showArtwork 66 | focus: true 67 | 68 | holder.emoji: "qrc:/assets/media-album-track.svg" 69 | holder.title : "Oops!" 70 | holder.body: i18n("This list is empty") 71 | 72 | headBar.visible: true 73 | headBar.farLeftContent: ToolButton 74 | { 75 | icon.name: "go-previous" 76 | text: control.prefix === "album" ? i18n("Albums") : i18n("Artists") 77 | onClicked: control.pop() 78 | } 79 | 80 | onQueueTrack: (index) => Player.queueTracks([listModel.get(index)], index) 81 | onRowClicked: (index) => Player.quickPlay(listModel.get(index)) 82 | onAppendTrack: (index) => Player.addTrack(listModel.get(index)) 83 | 84 | onPlayAll: 85 | { 86 | control.pop() 87 | Player.playAllModel(listModel.list) 88 | } 89 | 90 | onAppendAll: 91 | { 92 | control.pop() 93 | Player.appendAllModel(listModel.list) 94 | } 95 | 96 | onShuffleAll: 97 | { 98 | control.pop() 99 | Player.shuffleAllModel(listModel.list) 100 | } 101 | } 102 | } 103 | 104 | function populateTable(album, artist) 105 | { 106 | control.push(_tracksTableComponent) 107 | 108 | currentAlbum = album === undefined ? "" : album 109 | currentArtist = artist 110 | 111 | var query 112 | if(currentAlbum && currentArtist) 113 | { 114 | query = Q.GET.albumTracks_.arg(currentAlbum) 115 | query = query.arg(currentArtist) 116 | 117 | }else if(currentArtist && !currentAlbum.length) 118 | { 119 | query = Q.GET.artistTracks_.arg(currentArtist) 120 | } 121 | 122 | console.log("GET ARTIST OR ALBUM BY", album, artist) 123 | control.currentQuery = query 124 | } 125 | 126 | function getFilterField() : Item 127 | { 128 | return control.currentItem.getFilterField() 129 | } 130 | 131 | function getGoBackFunc() 132 | { 133 | if (control.depth > 1) 134 | return () => { control.pop() } 135 | else 136 | return null 137 | } 138 | } 139 | 140 | -------------------------------------------------------------------------------- /src/widgets/BabeTable/TableDelegate.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2 | import QtQuick.Controls 3 | import QtQuick.Layouts 4 | 5 | import org.mauikit.controls as Maui 6 | 7 | Maui.ListBrowserDelegate 8 | { 9 | id: control 10 | 11 | property bool number : false 12 | property bool coverArt : false 13 | 14 | readonly property string artist : model.artist 15 | readonly property string album : model.album 16 | readonly property string title : model.title 17 | readonly property url url : model.url 18 | readonly property int track : model.track 19 | 20 | property bool sameAlbum : false 21 | property bool appendButton : false 22 | 23 | signal appendClicked() 24 | 25 | maskRadius: Maui.Style.radiusV 26 | 27 | isCurrentItem: ListView.isCurrentItem || checked 28 | draggable: true 29 | iconSource: "media-album-cover" 30 | 31 | template.isMask: true 32 | 33 | label1.text: control.number ? control.track + ". " + control.title : control.title 34 | label2.text: control.artist + " | " + control.album 35 | label2.visible: control.coverArt ? !control.sameAlbum : true 36 | 37 | iconVisible: !control.sameAlbum && control.coverArt 38 | imageSource: coverArt ? "image://artwork/album:"+ control.artist+":"+control.album : "" 39 | 40 | ToolButton 41 | { 42 | visible: control.appendButton 43 | icon.name: "list-add" 44 | onClicked: control.appendClicked() 45 | icon.color: delegate.label1.color 46 | flat: true 47 | icon.width: 16 48 | icon.height: 16 49 | padding: 0 50 | opacity: delegate.hovered ? 0.8 : 0.6 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/widgets/BabeTable/TableMenu.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2 | import QtQuick.Controls 3 | 4 | import org.mauikit.controls as Maui 5 | import org.mauikit.filebrowsing as FB 6 | 7 | Maui.ContextualMenu 8 | { 9 | id: control 10 | 11 | property bool fav : false 12 | property int index 13 | property var titleInfo 14 | 15 | signal favClicked() 16 | signal queueClicked() 17 | signal saveToClicked() 18 | signal openWithClicked() 19 | signal editClicked() 20 | signal shareClicked() 21 | signal selectClicked() 22 | signal infoClicked() 23 | signal copyToClicked() 24 | signal deleteClicked() 25 | 26 | property alias menuItem : control.contentData 27 | 28 | title: control.titleInfo.title 29 | Maui.Controls.subtitle: control.titleInfo.artist 30 | icon.source: "image://artwork/album:"+ control.titleInfo.artist+":"+control.titleInfo.album 31 | 32 | Maui.MenuItemActionRow 33 | { 34 | Action 35 | { 36 | text: !fav ? i18n("Fav it"): i18n("UnFav it") 37 | checked: control.fav 38 | checkable: true 39 | icon.name: "love" 40 | onTriggered: favClicked() 41 | } 42 | 43 | 44 | Action 45 | { 46 | text: i18n("Edit") 47 | icon.name: "document-edit" 48 | onTriggered: 49 | { 50 | editClicked() 51 | } 52 | } 53 | 54 | Action 55 | { 56 | text: i18n("Share") 57 | icon.name: "document-share" 58 | onTriggered: shareClicked() 59 | } 60 | } 61 | 62 | MenuItem 63 | { 64 | visible: root.lastUsedPlaylist.length > 0 65 | height: visible ? implicitHeight : -control.spacing 66 | text: i18n("Add to '%1'", root.lastUsedPlaylist) 67 | icon.name: "tag" 68 | onTriggered: 69 | { 70 | FB.Tagging.tagUrl(control.titleInfo.url, root.lastUsedPlaylist) 71 | } 72 | } 73 | 74 | Action 75 | { 76 | text: i18n("Add to playlist") 77 | icon.name: "tag" 78 | onTriggered: saveToClicked() 79 | shortcut: "Ctrl+S" 80 | } 81 | 82 | MenuSeparator {} 83 | 84 | MenuItem 85 | { 86 | text: i18n("Select") 87 | icon.name: "item-select" 88 | onTriggered: 89 | { 90 | selectionBar.addToSelection(listModel.get(control.index)) 91 | selectionMode = Maui.Handy.isTouch 92 | } 93 | } 94 | 95 | MenuSeparator {} 96 | 97 | MenuItem 98 | { 99 | text: i18n("Play Next") 100 | icon.name: "view-media-recent" 101 | onTriggered: 102 | { 103 | queueClicked() 104 | } 105 | } 106 | 107 | MenuSeparator{} 108 | 109 | 110 | 111 | // MenuItem 112 | // { 113 | // enabled: Maui.App.handleAccounts 114 | // text: i18n("Copy to cloud") 115 | // onTriggered: 116 | // { 117 | // copyToClicked() 118 | // close() 119 | // } 120 | // } 121 | 122 | MenuItem 123 | { 124 | text: i18n("Show in Folder") 125 | icon.name: "folder-open" 126 | enabled: !Maui.Handy.isAndroid 127 | onTriggered: 128 | { 129 | openWithClicked() 130 | } 131 | } 132 | 133 | MenuSeparator {} 134 | 135 | 136 | 137 | // Maui.MenuItem 138 | // { 139 | // text: i18n("Info...") 140 | // onTriggered: 141 | // { 142 | // infoClicked() 143 | // close() 144 | // } 145 | // } 146 | 147 | 148 | MenuItem 149 | { 150 | text: i18n("Delete") 151 | icon.name: "edit-delete" 152 | Maui.Controls.status: Maui.Controls.Negative 153 | onTriggered: 154 | { 155 | deleteClicked() 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/widgets/FoldersView/FoldersView.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2 | import QtQuick.Controls 3 | import QtQuick.Layouts 4 | 5 | import org.mauikit.controls as Maui 6 | 7 | import "../BabeTable" 8 | import "../../db/Queries.js" as Q 9 | import "../../utils/Player.js" as Player 10 | 11 | import org.maui.vvave 12 | 13 | StackView 14 | { 15 | id: control 16 | 17 | property string currentFolder : "" 18 | readonly property Flickable flickable: currentItem.flickable 19 | 20 | initialItem: Maui.Page 21 | { 22 | Maui.Theme.colorSet: Maui.Theme.View 23 | Maui.Theme.inherit: false 24 | Maui.Controls.level : Maui.Controls.Secondary 25 | 26 | headerContainer.margins: Maui.Style.contentMargins 27 | headerContainer.topMargin: 0 28 | 29 | headBar.middleContent: Maui.SearchField 30 | { 31 | id: _filterField 32 | Layout.fillWidth: true 33 | Layout.maximumWidth: 500 34 | Layout.alignment: Qt.AlignCenter 35 | 36 | placeholderText: i18np("Filter %1 folder", "Filter %1 folders", _foldersList.count) 37 | 38 | KeyNavigation.up: browser 39 | KeyNavigation.down: browser 40 | 41 | onAccepted: browser.model.filter = text 42 | onCleared: browser.model.filter = text 43 | } 44 | 45 | Maui.Holder 46 | { 47 | anchors.fill: parent 48 | visible: _foldersList.count === 0 49 | emoji: "qrc:/assets/dialog-information.svg" 50 | title : i18n("No Folders!") 51 | body: i18n("Add new music to your sources to browse by folders") 52 | Action 53 | { 54 | text: i18n("Add sources") 55 | onTriggered: openSettingsDialog() 56 | } 57 | } 58 | 59 | Maui.ListBrowser 60 | { 61 | id: browser 62 | 63 | anchors.fill: parent 64 | 65 | model: Maui.BaseModel 66 | { 67 | sort: "label" 68 | sortOrder: Qt.AscendingOrder 69 | recursiveFilteringEnabled: true 70 | sortCaseSensitivity: Qt.CaseInsensitive 71 | filterCaseSensitivity: Qt.CaseInsensitive 72 | 73 | list: Folders 74 | { 75 | id: _foldersList 76 | folders: Vvave.folders 77 | } 78 | } 79 | 80 | section.property: browser.model.sort 81 | section.criteria: ViewSection.FirstCharacter 82 | section.delegate: Maui.LabelDelegate 83 | { 84 | isSection: true 85 | width: ListView.view.width 86 | text: String(section) 87 | } 88 | 89 | delegate: Maui.ListBrowserDelegate 90 | { 91 | width: ListView.view.width 92 | template.headerSizeHint: Maui.Style.rowHeight 93 | isCurrentItem: ListView.isCurrentItem 94 | iconSizeHint: Maui.Style.iconSizes.medium 95 | label1.text: model.label 96 | label2.text: model.path.replace("file://", "") 97 | label2.wrapMode: Text.Wrap 98 | iconSource: model.icon 99 | template.isMask: true 100 | 101 | onClicked: 102 | { 103 | browser.currentIndex = index 104 | if(Maui.Handy.singleClick) 105 | { 106 | filter(model.path) 107 | } 108 | } 109 | 110 | onDoubleClicked: 111 | { 112 | browser.currentIndex = index 113 | if(!Maui.Handy.singleClick) 114 | { 115 | filter(model.path) 116 | } 117 | } 118 | } 119 | } 120 | function getFilterField() : Item 121 | { 122 | return _filterField 123 | } 124 | } 125 | 126 | Component 127 | { 128 | id: _filterListComponent 129 | 130 | BabeTable 131 | { 132 | list.query : Q.GET.tracksWhere_.arg("source = \""+control.currentFolder+"\"") 133 | 134 | coverArtVisible: true 135 | 136 | holder.emoji: "qrc:/assets/dialog-information.svg" 137 | holder.isMask: true 138 | holder.title : i18n("No Tracks!") 139 | holder.body: i18n("This source folder seems to be empty!") 140 | 141 | headBar.visible: true 142 | headBar.farLeftContent: ToolButton 143 | { 144 | icon.name: "go-previous" 145 | onClicked: control.pop() 146 | } 147 | 148 | onRowClicked: (index) => Player.quickPlay(listModel.get(index)) 149 | onAppendTrack: (index) => Player.addTrack(listModel.get(index)) 150 | onQueueTrack: (index) => Player.queueTracks([listModel.get(index)], index) 151 | 152 | onPlayAll: Player.playAllModel(listModel.list) 153 | onAppendAll: Player.appendAllModel(listModel.list) 154 | onShuffleAll: Player.shuffleAllModel(listModel.list) 155 | } 156 | } 157 | 158 | function filter(folder) 159 | { 160 | currentFolder = folder 161 | control.push(_filterListComponent) 162 | } 163 | 164 | function getFilterField() : Item 165 | { 166 | return control.currentItem.getFilterField() 167 | } 168 | 169 | function getGoBackFunc() 170 | { 171 | if (control.depth > 1) 172 | return () => { control.pop() } 173 | else 174 | return null 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /src/widgets/InfoView/InfoView.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2 | import QtQuick.Controls 3 | import QtQuick.Layouts 4 | 5 | import org.mauikit.controls as Maui 6 | 7 | import org.maui.vvave as Vvave 8 | 9 | Maui.Page 10 | { 11 | id: control 12 | 13 | Vvave.TrackInfo 14 | { 15 | id: _trackInfo 16 | track : root.currentTrack 17 | } 18 | 19 | Maui.ScrollColumn 20 | { 21 | anchors.fill: parent 22 | spacing: Maui.Style.space.big 23 | clip: true 24 | 25 | Maui.SectionHeader 26 | { 27 | Layout.fillWidth: true 28 | // maskRadius: Maui.Style.radiusV 29 | 30 | label1.text: currentTrack.artist 31 | // label1.font.pointSize: Maui.Style.fontSizes.huge 32 | // label1.font.bold: true 33 | // label1.font.weight: Font.Black 34 | label2.text: i18n("Artist Info") 35 | 36 | // imageSource: "image://artwork/artist:" + currentTrack.artist 37 | // iconSizeHint: Maui.Style.iconSizes.huge 38 | } 39 | 40 | TextArea 41 | { 42 | Layout.fillWidth: true 43 | Layout.fillHeight: true 44 | readOnly: true 45 | text: _trackInfo.artistWiki 46 | wrapMode: Text.WrapAtWordBoundaryOrAnywhere 47 | textFormat: Text.RichText 48 | 49 | background: null 50 | } 51 | 52 | Maui.SectionHeader 53 | { 54 | Layout.fillWidth: true 55 | // maskRadius: Maui.Style.radiusV 56 | 57 | label1.text: currentTrack.album 58 | // label1.font.pointSize: Maui.Style.fontSizes.huge 59 | // label1.font.bold: true 60 | // label1.font.weight: Font.Black 61 | label2.text: i18n("Album Info") 62 | // imageSource: "image://artwork/album:" + currentTrack.artist+":"+currentTrack.album 63 | // iconSizeHint: Maui.Style.iconSizes.huge 64 | } 65 | 66 | TextArea 67 | { 68 | Layout.fillWidth: true 69 | Layout.fillHeight: true 70 | readOnly: true 71 | text: _trackInfo.albumWiki 72 | wrapMode: Text.WrapAtWordBoundaryOrAnywhere 73 | textFormat: Text.RichText 74 | 75 | background: null 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/widgets/InfoView/LyricsView.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2 | import QtQuick.Controls 3 | import org.mauikit.controls as Maui 4 | 5 | Item 6 | { 7 | id: infoRoot 8 | 9 | anchors.centerIn: parent 10 | 11 | property string lyrics 12 | 13 | Rectangle 14 | { 15 | anchors.fill: parent 16 | z: -999 17 | color: backgroundColor 18 | } 19 | 20 | Text 21 | { 22 | text: lyrics || "Nothing here" 23 | color: foregroundColor 24 | font.pointSize: Maui.Style.fontSizes.big 25 | horizontalAlignment: Qt.AlignHCenter 26 | textFormat: Text.StyledText 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/widgets/MetadataDialog.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2 | import QtQuick.Controls 3 | import QtQuick.Layouts 4 | 5 | import org.mauikit.controls as Maui 6 | 7 | Maui.InfoDialog 8 | { 9 | id: control 10 | 11 | property var data : control.model.get(control.index) 12 | property int index : -1 //index of the item in the model TracksModel 13 | 14 | property Maui.BaseModel model 15 | 16 | // title: i18n("Edit") 17 | 18 | signal edited(var data, int index) 19 | 20 | standardButtons: Dialog.Ok | Dialog.Cancel 21 | 22 | onAccepted: 23 | { 24 | control.data.title = _titleField.text; 25 | control.data.artist = _artistField.text; 26 | control.data.album = _albumField.text; 27 | control.data.track = _trackField.text; 28 | control.data.genre = _genreField.text; 29 | control.data.releasedate = _yearField.text; 30 | control.data.comment = _commentField.text; 31 | 32 | control.edited(control.data, control.index) 33 | control.close() 34 | } 35 | 36 | onRejected: close() 37 | 38 | Maui.SectionGroup 39 | { 40 | id: _template 41 | title: i18n("Metadata") 42 | description: i18n("Embedded metadata info.") 43 | 44 | Maui.SectionItem 45 | { 46 | label1.text: i18n("Track Title") 47 | 48 | TextField 49 | { 50 | id: _titleField 51 | text: control.data.title 52 | Layout.fillWidth: true 53 | } 54 | } 55 | 56 | Maui.SectionItem 57 | { 58 | label1.text: i18n("Artist") 59 | 60 | TextField 61 | { 62 | id: _artistField 63 | text: control.data.artist 64 | Layout.fillWidth: true 65 | 66 | } 67 | } 68 | 69 | Maui.SectionItem 70 | { 71 | label1.text: i18n("Album") 72 | 73 | TextField 74 | { 75 | id: _albumField 76 | text: control.data.album 77 | Layout.fillWidth: true 78 | 79 | } 80 | } 81 | 82 | Maui.SectionItem 83 | { 84 | label1.text: i18n("Track") 85 | 86 | TextField 87 | { 88 | id: _trackField 89 | text: control.data.track 90 | Layout.fillWidth: true 91 | 92 | } 93 | } 94 | 95 | Maui.SectionItem 96 | { 97 | label1.text: i18n("Genre") 98 | 99 | TextField 100 | { 101 | id: _genreField 102 | text: control.data.genre 103 | Layout.fillWidth: true 104 | 105 | } 106 | } 107 | 108 | Maui.SectionItem 109 | { 110 | label1.text: i18n("Year") 111 | 112 | TextField 113 | { 114 | id: _yearField 115 | text: control.data.releasedate 116 | Layout.fillWidth: true 117 | 118 | } 119 | } 120 | 121 | Maui.SectionItem 122 | { 123 | label1.text: i18n("Comment") 124 | 125 | TextField 126 | { 127 | id: _commentField 128 | text: control.data.comment 129 | Layout.fillWidth: true 130 | 131 | } 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/widgets/MiniMode.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2 | import QtQuick.Controls 3 | import QtQuick.Window 4 | 5 | import org.mauikit.controls as Maui 6 | import org.mauikit.filebrowsing as FB 7 | 8 | import org.maui.vvave as Vvave 9 | 10 | import "../utils/Player.js" as Player 11 | 12 | Maui.BaseWindow 13 | { 14 | id: control 15 | 16 | minimumHeight: 200 17 | maximumHeight: 200 18 | 19 | minimumWidth: 200 20 | maximumWidth: 200 21 | 22 | height: 200 23 | width: 200 24 | 25 | x: Screen.width - width - 50 26 | y: Screen.height - height - 50 27 | 28 | flags: Qt.Widget | Qt.WindowStaysOnTopHint | Qt.FramelessWindowHint 29 | 30 | Maui.Theme.colorSet: Maui.Theme.Complementary 31 | Maui.Theme.inherit: false 32 | 33 | Loader 34 | { 35 | anchors.fill: parent 36 | 37 | asynchronous: true 38 | sourceComponent: MouseArea 39 | { 40 | id: _mouseArea 41 | 42 | onDoubleClicked: toggleMiniMode() 43 | hoverEnabled: true 44 | 45 | Image 46 | { 47 | anchors.fill: parent 48 | source: "image://artwork/album:"+currentTrack.artist + ":"+ currentTrack.album 49 | fillMode: Image.PreserveAspectFit 50 | } 51 | 52 | ToolTip.delay: 1000 53 | ToolTip.timeout: 5000 54 | ToolTip.visible: _mouseArea.containsMouse 55 | ToolTip.text: root.title 56 | 57 | Control 58 | { 59 | Maui.Theme.colorSet: Maui.Theme.Complementary 60 | Maui.Theme.inherit: false 61 | 62 | anchors.fill: parent 63 | visible: _mouseArea.containsMouse 64 | background: Rectangle 65 | { 66 | color: "#000000" 67 | opacity: 0.7 68 | } 69 | 70 | Grid 71 | { 72 | Maui.Theme.colorSet: Maui.Theme.Complementary 73 | 74 | anchors.centerIn: parent 75 | columns: 2 76 | rows: 2 77 | rowSpacing: Maui.Style.space.medium 78 | columnSpacing: rowSpacing 79 | 80 | FB.FavButton 81 | { 82 | Maui.Theme.colorSet: Maui.Theme.Complementary 83 | Maui.Theme.inherit: false 84 | url: root.currentTrack.url 85 | icon.width: Maui.Style.iconSizes.big 86 | icon.height: Maui.Style.iconSizes.big 87 | } 88 | 89 | ToolButton 90 | { 91 | Maui.Theme.colorSet: Maui.Theme.Complementary 92 | Maui.Theme.inherit: false 93 | flat: true 94 | icon.width: Maui.Style.iconSizes.big 95 | icon.height: Maui.Style.iconSizes.big 96 | enabled: root.currentTrackIndex >= 0 97 | icon.color: Maui.Theme.textColor 98 | icon.name: player.playing ? "media-playback-pause" : "media-playback-start" 99 | onClicked: player.playing ? player.pause() : player.play() 100 | } 101 | 102 | ToolButton 103 | { 104 | Maui.Theme.colorSet: Maui.Theme.Complementary 105 | Maui.Theme.inherit: false 106 | flat: true 107 | icon.width: Maui.Style.iconSizes.big 108 | icon.height: Maui.Style.iconSizes.big 109 | icon.name: "media-skip-forward" 110 | onClicked: Player.nextTrack() 111 | } 112 | 113 | ToolButton 114 | { 115 | Maui.Theme.colorSet: Maui.Theme.Complementary 116 | Maui.Theme.inherit: false 117 | 118 | flat: true 119 | icon.width: Maui.Style.iconSizes.big 120 | icon.height: Maui.Style.iconSizes.big 121 | 122 | icon.name: switch(playlist.playMode) 123 | { 124 | case Vvave.Playlist.Normal: return "media-playlist-normal" 125 | case Vvave.Playlist.Shuffle: return "media-playlist-shuffle" 126 | case Vvave.Playlist.Repeat: return "media-playlist-repeat" 127 | } 128 | onClicked: 129 | { 130 | switch(playlist.playMode) 131 | { 132 | case Vvave.Playlist.Normal: 133 | playlist.playMode = Vvave.Playlist.Shuffle 134 | break 135 | 136 | case Vvave.Playlist.Shuffle: 137 | playlist.playMode = Vvave.Playlist.Repeat 138 | break 139 | 140 | 141 | case Vvave.Playlist.Repeat: 142 | playlist.playMode = Vvave.Playlist.Normal 143 | break 144 | } 145 | } 146 | } 147 | } 148 | } 149 | } 150 | } 151 | 152 | 153 | DragHandler 154 | { 155 | grabPermissions: PointerHandler.CanTakeOverFromItems | PointerHandler.CanTakeOverFromHandlersOfDifferentType | PointerHandler.ApprovesTakeOverByAnything 156 | onActiveChanged: 157 | { 158 | if (active) 159 | { 160 | control.startSystemMove() 161 | } 162 | } 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /src/widgets/PlaylistsView/PlaylistsView.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2 | import QtQuick.Controls 3 | 4 | import org.mauikit.controls as Maui 5 | import org.mauikit.filebrowsing as FB 6 | 7 | import org.maui.vvave as Vvave 8 | 9 | import "../BabeTable" 10 | 11 | import "../../db/Queries.js" as Q 12 | import "../../utils/Player.js" as Player 13 | 14 | StackView 15 | { 16 | id: control 17 | 18 | property string playlistQuery 19 | 20 | readonly property Flickable flickable : currentItem.flickable 21 | readonly property alias playlistList : _playlistPage.list 22 | 23 | Component 24 | { 25 | id: newPlaylistDialogComponent 26 | FB.NewTagDialog {onClosed: destroy()} 27 | } 28 | 29 | initialItem: PlaylistsViewModel 30 | { 31 | id: _playlistPage 32 | } 33 | 34 | Component 35 | { 36 | id: _filterListComponent 37 | 38 | BabeTable 39 | { 40 | id: filterList 41 | 42 | property string currentPlaylist //id 43 | 44 | property bool isPublic: true 45 | 46 | signal removeFromPlaylist(string url) 47 | 48 | coverArtVisible: settings.showArtwork 49 | 50 | list.query: control.playlistQuery 51 | showTitle: false 52 | title: currentPlaylist 53 | 54 | holder.emoji: "qrc:/assets/dialog-information.svg" 55 | holder.isMask: true 56 | holder.title : title 57 | holder.body: i18n("Your playlist is empty. Start adding new music to it") 58 | 59 | headBar.visible: true 60 | headBar.farLeftContent: ToolButton 61 | { 62 | icon.name: "go-previous" 63 | onClicked: control.pop() 64 | } 65 | 66 | contextMenuItems: MenuItem 67 | { 68 | text: i18n("Remove from playlist") 69 | onTriggered: 70 | { 71 | control.playlistList.removeTrack(currentPlaylist, listModel.get(filterList.currentIndex).url) 72 | listModel.list.remove(filterList.currentIndex) 73 | } 74 | } 75 | 76 | onQueueTrack: (index) => Player.queueTracks([listModel.get(index)], index) 77 | onRowClicked: (index) => Player.quickPlay(filterList.listModel.get(index)) 78 | onAppendTrack: (index) => Player.addTrack(filterList.listModel.get(index)) 79 | 80 | onPlayAll: 81 | { 82 | Player.playAllModel(listModel.list) 83 | control.pop() 84 | 85 | if(filterList.isPublic) 86 | { 87 | root.sync = true 88 | root.syncPlaylist = currentPlaylist 89 | } 90 | } 91 | 92 | onAppendAll: Player.appendAllModel(listModel.list) 93 | onShuffleAll: Player.shuffleAllModel(listModel.list) 94 | 95 | Component.onCompleted: 96 | { 97 | isPublic = false 98 | 99 | switch(currentPlaylist) 100 | { 101 | case "mostPlayed": 102 | playlistQuery = Q.GET.mostPlayedTracks 103 | filterList.listModel.sort = "title" 104 | break; 105 | 106 | case "randomTracks": 107 | filterList.listModel.sort = "title" 108 | playlistQuery = Q.GET.randomTracks_; 109 | break; 110 | 111 | case "recentTracks": 112 | playlistQuery = Q.GET.recentTracks_; 113 | filterList.listModel.sort = "title" 114 | break; 115 | 116 | case "neverPlayed": 117 | playlistQuery = Q.GET.neverPlayedTracks_; 118 | filterList.listModel.sort = "title" 119 | break; 120 | 121 | case "classicTracks": 122 | playlistQuery = Q.GET.oldTracks; 123 | filterList.listModel.sort = "title" 124 | break; 125 | 126 | default: 127 | isPublic = true 128 | playlistQuery = Q.GET.playlistTracks_.arg(currentPlaylist) 129 | break; 130 | } 131 | 132 | filterList.isPublic = isPublic 133 | filterList.listModel.clearFilters() 134 | } 135 | } 136 | } 137 | 138 | function populate(playlist, isPublic) 139 | { 140 | control.push(_filterListComponent, {'currentPlaylist': playlist, 'isPublic': isPublic}) 141 | } 142 | 143 | function getFilterField() : Item 144 | { 145 | return control.currentItem.getFilterField() 146 | } 147 | 148 | function getGoBackFunc() 149 | { 150 | if (control.depth > 1) 151 | return () => { control.pop() } 152 | else 153 | return null 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/widgets/SelectionBar.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2 | import QtQuick.Controls 3 | 4 | import org.mauikit.controls as Maui 5 | 6 | import "../utils/Player.js" as Player 7 | import "BabeTable" 8 | 9 | Maui.SelectionBar 10 | { 11 | id: control 12 | 13 | listDelegate: TableDelegate 14 | { 15 | isCurrentItem: false 16 | Maui.Theme.inherit: true 17 | width: ListView.view.width 18 | number: false 19 | coverArt: true 20 | checked: true 21 | checkable: true 22 | onToggled: control.removeAtIndex(index) 23 | background: Item {} 24 | } 25 | 26 | Action 27 | { 28 | text: i18n("Play") 29 | icon.name: "media-playback-start" 30 | onTriggered: 31 | { 32 | mainPlaylist.listModel.list.clear() 33 | Player.playAll(control.items) 34 | } 35 | } 36 | 37 | Action 38 | { 39 | text: i18n("Append") 40 | icon.name: "media-playlist-append" 41 | onTriggered: Player.appendAll(control.items) 42 | } 43 | 44 | Action 45 | { 46 | text: i18n("Tags") 47 | icon.name: "tag" 48 | onTriggered: tagUrls(control.uris) 49 | } 50 | 51 | hiddenActions: [ 52 | Action 53 | { 54 | text: i18n("Share") 55 | icon.name: "document-share" 56 | onTriggered: Maui.Platform.shareFiles(control.uris) 57 | }, 58 | 59 | Action 60 | { 61 | text: i18n("Queue") 62 | icon.name: "view-media-recent" 63 | onTriggered: 64 | { 65 | Player.queueTracks(control.items) 66 | } 67 | }, 68 | 69 | Action 70 | { 71 | text: i18n("Remove") 72 | icon.name: "edit-delete" 73 | Maui.Theme.textColor: Maui.Theme.negativeTextColor 74 | onTriggered: 75 | { 76 | var dialog = _removeDialogComponent.createObject(root) 77 | dialog.open() 78 | } 79 | } 80 | ] 81 | 82 | function addToSelection(item) 83 | { 84 | if(control.contains(String(item.url))) 85 | { 86 | control.removeAtUri(String(item.url)) 87 | return 88 | } 89 | 90 | item.thumbnail= item.artwork 91 | item.icon = "audio-x-generic" 92 | item.label= item.title 93 | item.mime= "image/png" 94 | item.tooltip= item.url 95 | item.path= item.url 96 | 97 | control.append(item.url, item) 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/widgets/SettingsView/ShortcutsDialog.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2 | import QtQuick.Controls 3 | import QtQuick.Layouts 4 | 5 | import org.mauikit.controls as Maui 6 | 7 | import org.maui.vvave 8 | 9 | Maui.PopupPage 10 | { 11 | id: control 12 | title: i18n("Shortcuts") 13 | persistent: false 14 | page.showTitle: false 15 | headBar.visible: false 16 | scrollView.padding: 0 17 | 18 | maxHeight: 500 // Copied from Nota. I don't like hardcoded layout, though. 19 | maxWidth: 350 20 | 21 | Component 22 | { 23 | id: _shortcutCategoryComponent 24 | Maui.SectionGroup 25 | { 26 | title: i18n("Unknown") 27 | function setTitle(rawtext: string) 28 | { 29 | this.title = i18n(rawtext) 30 | } 31 | } 32 | } 33 | 34 | Component 35 | { 36 | id: _shortcutLabelComponent 37 | Maui.FlexSectionItem 38 | { 39 | label1.text: i18n("Unknown") 40 | 41 | Maui.ToolActions 42 | { 43 | id: _actions 44 | checkable: false 45 | autoExclusive: false 46 | } 47 | 48 | function setText(rawtext: string) 49 | { 50 | this.label1.text = i18n(rawtext) 51 | } 52 | 53 | function addKeys(keynames) 54 | { 55 | for (let name of keynames) { 56 | _actions.actions.push( 57 | _shortcutComboComponent.createObject( 58 | _actions, 59 | {text: name} // (Probably no `i18n`?) 60 | ) 61 | ) 62 | } 63 | } 64 | } 65 | } 66 | 67 | Component 68 | { 69 | id: _shortcutComboComponent 70 | Action {} 71 | } 72 | 73 | Component.onCompleted: { 74 | let categories = [] 75 | let category_shortcuts = {} 76 | for (let i = 0; i < shortcuts.length; i++) 77 | { 78 | let sc = shortcuts[i] 79 | if (!(sc.dialogCategory in category_shortcuts)) { 80 | categories.push(sc.dialogCategory) 81 | category_shortcuts[sc.dialogCategory] = [] 82 | } 83 | var cat = category_shortcuts[sc.dialogCategory] 84 | 85 | var combo = sc.nativeText.split("+").map((key) => key == "" ? "+" : key).join("\n").replace('/\+\n\+/g', "+").split("\n") 86 | cat.push({ 87 | label: sc.dialogLabel, 88 | // combo: sc.nativeText.split(/(?<=[^\+])\+|\+(?=[^\+])/) 89 | combo: combo 90 | // Split on "+" but try to handle shortcuts that include a literal [+] key. 91 | // QML doesn't like lookbehinds, so we get this chain. 92 | }) 93 | } 94 | 95 | for (let category of categories) { 96 | let section = _shortcutCategoryComponent.createObject(control) 97 | console.log("Trying ot push to scollable") 98 | scrollable.push(section) 99 | section.setTitle(category) 100 | for (let shortcut of category_shortcuts[category]) { 101 | let label = _shortcutLabelComponent.createObject(section) 102 | section.content.push(label) 103 | label.setText(shortcut.label) 104 | label.addKeys(shortcut.combo) 105 | } 106 | } 107 | 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/widgets/SleepTimerDialog.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2 | import QtQuick.Controls 3 | import QtQuick.Layouts 4 | 5 | import org.mauikit.controls as Maui 6 | 7 | Maui.PopupPage 8 | { 9 | id: control 10 | 11 | title: i18n("Sleep Timer") 12 | persistent: false 13 | 14 | component OptionEntry : CheckBox 15 | { 16 | Layout.fillWidth: true 17 | checkable: true 18 | autoExclusive: true 19 | } 20 | 21 | property string option : settings.sleepOption 22 | 23 | 24 | ButtonGroup 25 | { 26 | id: _group 27 | } 28 | 29 | OptionEntry 30 | { 31 | ButtonGroup.group: _group 32 | text: i18n("15 minutes") 33 | checked: settings.sleepOption === "15m" 34 | onToggled: () => 35 | { 36 | if(checked) 37 | { 38 | control.option = "15m" 39 | }else 40 | { 41 | control.option = "none" 42 | } 43 | } 44 | } 45 | 46 | OptionEntry 47 | { 48 | ButtonGroup.group: _group 49 | text: i18n("30 minutes") 50 | checked: settings.sleepOption === "30m" 51 | 52 | onToggled: () => 53 | { 54 | if(checked) 55 | { 56 | control.option = "30m" 57 | }else 58 | { 59 | control.option = "none" 60 | } 61 | } 62 | } 63 | 64 | OptionEntry 65 | { 66 | ButtonGroup.group: _group 67 | text: i18n("1 hour") 68 | checked: settings.sleepOption === "60m" 69 | 70 | onToggled: () => 71 | { 72 | if(checked) 73 | { 74 | control.option = "60m" 75 | }else 76 | { 77 | control.option = "none" 78 | } 79 | } 80 | } 81 | 82 | OptionEntry 83 | { 84 | ButtonGroup.group: _group 85 | text: i18n("End of track") 86 | checked: settings.sleepOption === "eot" 87 | 88 | onToggled: () => 89 | { 90 | if(checked) 91 | { 92 | control.option = "eot" 93 | }else 94 | { 95 | control.option = "none" 96 | } 97 | } 98 | } 99 | 100 | OptionEntry 101 | { 102 | ButtonGroup.group: _group 103 | text: i18n("End of playlist") 104 | checked: settings.sleepOption === "eop" 105 | 106 | onToggled: () => 107 | { 108 | if(checked) 109 | { 110 | control.option = "eop" 111 | }else 112 | { 113 | control.option = "none" 114 | } 115 | } 116 | } 117 | 118 | OptionEntry 119 | { 120 | ButtonGroup.group: _group 121 | checked: settings.sleepOption === "none" 122 | 123 | text: i18n("Off") 124 | onToggled: () => 125 | { 126 | if(checked) 127 | { 128 | control.option = "none" 129 | }else 130 | { 131 | control.option = "none" 132 | } 133 | } 134 | } 135 | 136 | MenuSeparator 137 | { 138 | 139 | } 140 | 141 | CheckBox 142 | { 143 | enabled: control.option !== "none" 144 | Layout.fillWidth: true 145 | text: i18n("Close application after") 146 | checked: settings.closeAfterSleep 147 | onToggled: settings.closeAfterSleep = checked 148 | } 149 | 150 | 151 | actions: [ 152 | Action 153 | { 154 | text: i18n("Cancel") 155 | onTriggered: control.close() 156 | }, 157 | 158 | Action 159 | { 160 | text: i18n("Set") 161 | onTriggered: 162 | { 163 | setSleepTimer(control.option) 164 | control.close() 165 | } 166 | } 167 | ] 168 | } 169 | -------------------------------------------------------------------------------- /src/widgets/TracksView.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2 | import QtQuick.Controls 3 | 4 | import org.mauikit.controls as Maui 5 | import org.maui.vvave as Vvave 6 | 7 | import "BabeTable" 8 | import "BabeGrid" 9 | 10 | import "../db/Queries.js" as Q 11 | import "../utils/Player.js" as Player 12 | 13 | BabeTable 14 | { 15 | trackNumberVisible: false 16 | coverArtVisible: false 17 | 18 | holder.emoji: "qrc:/assets/dialog-information.svg" 19 | holder.title : i18n("No Tracks!") 20 | holder.body: i18n("Add new music sources") 21 | holder.actions:[ 22 | 23 | Action 24 | { 25 | text: i18n("Add sources") 26 | onTriggered: openSettingsDialog() 27 | }, 28 | 29 | Action 30 | { 31 | text: i18n("Open file") 32 | } 33 | ] 34 | 35 | list.query : Q.GET.allTracks 36 | listModel.sort : "artist" 37 | listModel.sortOrder : Qt.AscendingOrder 38 | group: true 39 | 40 | onRowClicked: (index) => Player.quickPlay(listModel.get(index)) 41 | onAppendTrack: (index) => Player.addTrack(listModel.get(index)) 42 | onQueueTrack:(index) => Player.queueTracks([listModel.get(index)], index) 43 | 44 | onPlayAll: Player.playAllModel(listModel.list) 45 | onAppendAll: Player.appendAllModel(listModel.list) 46 | onShuffleAll: Player.shuffleAllModel(listModel.list) 47 | } 48 | -------------------------------------------------------------------------------- /src/windows_files/README: -------------------------------------------------------------------------------- 1 | Shows how to add an entry to the Windows start menu. 2 | 3 | Generate installer with 4 | 5 | binarycreator --offline-only -c config/config.xml -p packages installer 6 | C:\Qt\QtIFW2.0.1\bin\binarycreator.exe --offline-only -c config\config.xml -p packages VvaveInstaller.exe 7 | -------------------------------------------------------------------------------- /src/windows_files/config/config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Vvave 4 | 1.2.0 5 | Vvave Music Player 6 | Maui 7 | Vvave 8 | @ApplicationsDir@/Vvave 9 | 10 | -------------------------------------------------------------------------------- /src/windows_files/packages/org.maui.vvave/meta/installscript.qs: -------------------------------------------------------------------------------- 1 | /************************************************************************** 2 | ** 3 | ** Copyright (C) 2015 The Qt Company Ltd. 4 | ** Contact: http://www.qt.io/licensing/ 5 | ** 6 | ** This file is part of the Qt Installer Framework. 7 | ** 8 | ** $QT_BEGIN_LICENSE:LGPL$ 9 | ** Commercial License Usage 10 | ** Licensees holding valid commercial Qt licenses may use this file in 11 | ** accordance with the commercial license agreement provided with the 12 | ** Software or, alternatively, in accordance with the terms contained in 13 | ** a written agreement between you and The Qt Company. For licensing terms 14 | ** and conditions see http://qt.io/terms-conditions. For further 15 | ** information use the contact form at http://www.qt.io/contact-us. 16 | ** 17 | ** GNU Lesser General Public License Usage 18 | ** Alternatively, this file may be used under the terms of the GNU Lesser 19 | ** General Public License version 2.1 or version 3 as published by the Free 20 | ** Software Foundation and appearing in the file LICENSE.LGPLv21 and 21 | ** LICENSE.LGPLv3 included in the packaging of this file. Please review the 22 | ** following information to ensure the GNU Lesser General Public License 23 | ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and 24 | ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. 25 | ** 26 | ** As a special exception, The Qt Company gives you certain additional 27 | ** rights. These rights are described in The Qt Company LGPL Exception 28 | ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. 29 | ** 30 | ** 31 | ** $QT_END_LICENSE$ 32 | ** 33 | **************************************************************************/ 34 | 35 | function Component() 36 | { 37 | // default constructor 38 | } 39 | 40 | Component.prototype.createOperations = function() 41 | { 42 | component.createOperations(); 43 | if (systemInfo.productType === "windows") { 44 | component.addOperation("CreateShortcut", 45 | "@TargetDir@/vvave.exe",// target 46 | "@DesktopDir@/Vvave.lnk",// link-path 47 | "workingDirectory=@TargetDir@",// working-dir 48 | "iconPath=@TargetDir@/vvave.exe", "iconId=0",// icon 49 | "description=Music Player");// description 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/windows_files/packages/org.maui.vvave/meta/package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Vvave 4 | Vvave Music Player 5 | 1.2.0 6 | 2020-11-20 7 | 8 | 9 | 10 | true 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/windows_files/vvave.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDE/vvave/ff68d3bd59e5594c2d0627bdd5e5416d7b614530/src/windows_files/vvave.ico --------------------------------------------------------------------------------