Music recognition toggle (like Shazam) with SongRec (#2301)

This commit is contained in:
end-4
2025-10-28 09:09:26 +01:00
committed by GitHub
7 changed files with 212 additions and 1 deletions
@@ -376,6 +376,11 @@ Singleton {
property int updateInterval: 3000
}
property JsonObject musicRecognition: JsonObject {
property int timeout: 16
property int interval: 4
}
property JsonObject search: JsonObject {
property int nonAppResultDelay: 30 // This prevents lagging when typing
property string engineBaseUrl: "https://www.google.com/search?q="
@@ -17,13 +17,17 @@ function findSuitableMaterialSymbol(summary = "") {
'time': 'scheduleb',
'installed': 'download',
'configuration reloaded': 'reset_wrench',
'unable': 'question_mark',
"couldn't": 'question_mark',
'config': 'reset_wrench',
'update': 'update',
'ai response': 'neurology',
'control': 'settings',
'upsca': 'compare',
'music': 'queue_music',
'install': 'deployed_code_update',
'startswith:file': 'folder_copy', // Declarative startsWith check
};
const lowerSummary = summary.toLowerCase();
@@ -54,6 +54,35 @@ ContentPage {
Config.options.resources.updateInterval = value;
}
}
}
ContentSection {
icon: "music_cast"
title: Translation.tr("Music Recognition")
ConfigSpinBox {
icon: "timer_off"
text: Translation.tr("Total duration timeout (s)")
value: Config.options.musicRecognition.timeout
from: 10
to: 100
stepSize: 2
onValueChanged: {
Config.options.musicRecognition.timeout = value;
}
}
ConfigSpinBox {
icon: "av_timer"
text: Translation.tr("Polling interval (s)")
value: Config.options.musicRecognition.interval
from: 2
to: 10
stepSize: 1
onValueChanged: {
Config.options.musicRecognition.interval = value;
}
}
}
ContentSection {
@@ -29,7 +29,7 @@ AbstractQuickPanel {
readonly property real baseCellHeight: 56
// Toggles
readonly property list<string> availableToggleTypes: ["network", "bluetooth", "idleInhibitor", "easyEffects", "nightLight", "darkMode", "cloudflareWarp", "gameMode", "screenSnip", "colorPicker", "onScreenKeyboard", "mic", "audio", "notifications", "powerProfile"]
readonly property list<string> availableToggleTypes: ["network", "bluetooth", "idleInhibitor", "easyEffects", "nightLight", "darkMode", "cloudflareWarp", "gameMode", "screenSnip", "colorPicker", "onScreenKeyboard", "mic", "audio", "notifications", "powerProfile","musicRecognition"]
readonly property int columns: Config.options.sidebar.quickToggles.android.columns
readonly property list<var> toggles: Config.ready ? Config.options.sidebar.quickToggles.android.toggles : []
readonly property list<var> toggleRows: toggleRowsForList(toggles)
@@ -0,0 +1,102 @@
import qs
import qs.modules.common
import qs.modules.common.widgets
import QtQuick
import Quickshell
import Quickshell.Io
import qs.services
AndroidQuickToggleButton {
id: root
property int timeoutInterval: Config.options.musicRecognition.interval
property int timeoutDuration: Config.options.musicRecognition.timeout
property string monitorSource: "monitor" // "monitor" (system sound) , "input" (microphone)
name: Translation.tr("Identify Music")
statusText: toggled ? Translation.tr("Listening...") : monitorSource === "monitor" ? Translation.tr("System sound") : Translation.tr("Microphone")
toggled: false
buttonIcon: toggled ? "music_cast" : (monitorSource === "monitor" ? "music_note" : "frame_person_mic")
property var recognizedTrack: ({ title:"", subtitle:"", url:""})
function handleRecognition(jsonText) {
try {
var obj = JSON.parse(jsonText)
root.recognizedTrack = {
title: obj.track.title,
subtitle: obj.track.subtitle,
url: obj.track.url
}
musicReconizedProc.running = true
} catch(e) {
Quickshell.execDetached(["notify-send", Translation.tr("Couldn't recognize music"), Translation.tr("Perhaps what you're listening to is too niche"), "-a", "Shell"])
} finally {
root.toggled = false
}
}
StyledToolTip {
text: Translation.tr("Recognize music | Right-click to toggle source")
}
onClicked: {
root.toggled = !root.toggled
recognizeMusicProc.running = root.toggled
musicReconizedProc.running = false
}
altAction: () => {
if (root.monitorSource === "monitor"){
root.monitorSource = "input"
return
}else {
root.monitorSource = "monitor"
}
}
Process {
id: recognizeMusicProc
running: false
command: [`${Directories.scriptPath}/musicRecognition/recognize-music.sh`, "-i", root.timeoutInterval, "-t", root.timeoutDuration, "-s", root.monitorSource]
stdout: StdioCollector {
onStreamFinished: {
handleRecognition(this.text)
}
}
onExited: (exitCode, exitStatus) => {
if (exitCode === 1) {
Quickshell.execDetached(["notify-send", Translation.tr("Couldn't recognize music"), Translation.tr("Make sure you have songrec installed"), "-a", "Shell"])
root.toggled = false
}
}
}
Process {
id: musicReconizedProc
running: false
command: [
"notify-send",
Translation.tr("Music Recognized"),
root.recognizedTrack.title + " - " + root.recognizedTrack.subtitle,
"-A", "Shazam",
"-A", "YouTube",
"-a", "Shell"
]
stdout: StdioCollector {
onStreamFinished: {
if (this.text === "") return
if (this.text == 0){
Qt.openUrlExternally(root.recognizedTrack.url);
} else {
Qt.openUrlExternally("https://www.youtube.com/results?search_query=" + root.recognizedTrack.title + " - " + root.recognizedTrack.subtitle);
}
}
}
}
}
@@ -232,4 +232,17 @@ DelegateChooser {
cellSize: modelData.size
} }
DelegateChoice { roleValue: "musicRecognition"; AndroidMusicRecognition {
required property int index
required property var modelData
buttonIndex: root.startingIndex + index
buttonData: modelData
editMode: root.editMode
expandedSize: modelData.size > 1
baseCellWidth: root.baseCellWidth
baseCellHeight: root.baseCellHeight
cellSpacing: root.spacing
cellSize: modelData.size
} }
}