-
-
Notifications
You must be signed in to change notification settings - Fork 135
Expand file tree
/
Copy pathapp_sem_grove.py
More file actions
173 lines (153 loc) · 5.46 KB
/
Copy pathapp_sem_grove.py
File metadata and controls
173 lines (153 loc) · 5.46 KB
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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# AudioMuse-AI - https://github.com/NeptuneHub/AudioMuse-AI
# Copyright (C) 2025 NeptuneHub
# SPDX-License-Identifier: AGPL-3.0-only
#
# This program is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License v3.0. See the LICENSE file
# in the project root or <https://github.com/NeptuneHub/AudioMuse-AI/blob/main/LICENSE>
"""Semantic and Groove (SemGrove) search Flask blueprint (sem_grove_bp).
Backs the "By Song" tab on the Lyrics Search page, delegating every query to
``tasks.sem_grove_manager`` and its merged lyrics+audio IVF index.
Main Features:
* Similarity search by seed song id, returning sorted tracks with the seed
itself flagged (``is_seed=true``).
* Endpoints to refresh the merged-index cache and report its stats for the UI.
"""
import logging
from flask import Blueprint, jsonify, request
logger = logging.getLogger(__name__)
sem_grove_bp = Blueprint("sem_grove_bp", __name__, template_folder="../templates")
@sem_grove_bp.route("/api/sem_grove/search", methods=["POST"])
def sem_grove_search_api():
"""
SemGrove similarity search by seed song.
---
tags:
- SemGrove
summary: Find songs similar to a seed song using the merged lyrics+audio IVF index.
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [item_id]
properties:
item_id:
type: string
description: Media server item id of the seed song.
limit:
type: integer
minimum: 1
maximum: 500
default: 50
responses:
200:
description: Sorted similar tracks (seed song included with `is_seed=true`).
content:
application/json:
schema:
type: object
properties:
results:
type: array
items:
type: object
properties:
item_id:
type: string
title:
type: string
author:
type: string
similarity:
type: number
format: float
is_seed:
type: boolean
count:
type: integer
400:
description: Missing or invalid request fields.
404:
description: Seed song not in the SemGrove index (requires both lyrics and audio analysis).
500:
description: Internal error.
"""
from tasks.sem_grove_manager import search_by_song
from app_helper import attach_song_features
try:
data = request.get_json() or {}
item_id = (data.get("item_id") or "").strip()
if not item_id:
return jsonify({"error": 'Missing "item_id".'}), 400
try:
limit = int(data.get("limit", 50))
except (TypeError, ValueError):
return jsonify({"error": 'Invalid "limit" value.'}), 400
limit = min(max(1, limit), 500)
results = search_by_song(item_id, limit=limit)
# results[0] is always the seed itself; if that's the only entry, no similar songs were found
similar_count = sum(1 for r in results if not r.get("is_seed"))
if not results or similar_count == 0:
return jsonify(
{
"error": "No similar songs found. "
"The song may not be in the SemGrove index yet "
"(requires both lyrics and audio analysis).",
"results": [],
}
), 404
attach_song_features(results)
return jsonify({"results": results, "count": len(results)})
except Exception:
logger.exception("SemGrove search failed")
return jsonify({"error": "An internal error occurred."}), 500
@sem_grove_bp.route("/api/sem_grove/cache/refresh", methods=["POST"])
def sem_grove_refresh_api():
"""
Refresh the SemGrove merged index.
---
tags:
- SemGrove
summary: Reload the merged lyrics+audio IVF index from the database.
responses:
200:
description: Refresh attempted; updated stats returned.
content:
application/json:
schema:
type: object
properties:
success:
type: boolean
stats:
type: object
500:
description: Internal error.
"""
from tasks.sem_grove_manager import get_sem_grove_stats, refresh_sem_grove_cache
try:
success = refresh_sem_grove_cache()
return jsonify({"success": success, "stats": get_sem_grove_stats()})
except Exception:
logger.exception("SemGrove cache refresh failed")
return jsonify({"success": False, "error": "Internal error."}), 500
@sem_grove_bp.route("/api/sem_grove/stats", methods=["GET"])
def sem_grove_stats_api():
"""
SemGrove index stats.
---
tags:
- SemGrove
summary: Return size and freshness metadata for the merged SemGrove index.
responses:
200:
description: Index statistics.
content:
application/json:
schema:
type: object
"""
from tasks.sem_grove_manager import get_sem_grove_stats
return jsonify(get_sem_grove_stats())