Skip to content

Commit 25161f4

Browse files
committed
query handling
1 parent dce3550 commit 25161f4

7 files changed

Lines changed: 226 additions & 104 deletions

File tree

‎Api/deps/db.py‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,20 @@
22

33
async def init_db():
44
await db_handler.initialize()
5+
try:
6+
ft_docs = await db_handler.get_collection("forum_topics").collection.find(
7+
{"topic_name": {"$exists": True, "$not": {"$regex": r"^topic_"}}}
8+
).to_list(100)
9+
for ft in ft_docs:
10+
t_id = ft.get("topic_id")
11+
t_name = ft.get("topic_name")
12+
if t_id and t_name:
13+
await db_handler.audio_collection.collection.update_many(
14+
{"topic_id": int(t_id), "topic_name": {"$regex": r"^topic_"}},
15+
{"$set": {"topic_name": t_name}},
16+
)
17+
except Exception:
18+
pass
519

620
def get_audio_tracks_collection():
721
return db_handler.audio_collection.collection

‎Api/services/track_service.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ async def browse_tracks(
197197

198198

199199
_TOPICS_CACHE: dict[str, tuple[float, dict]] = {}
200-
_TOPICS_CACHE_TTL = 60.0
200+
_TOPICS_CACHE_TTL = 120.0
201201

202202

203203
async def get_unique_topics(
@@ -347,7 +347,7 @@ async def browse_topic_tracks(
347347
"topic_id": 1,
348348
"topic_name": 1,
349349
}
350-
sort = [("created_at", -1), ("source_message_id", -1), ("_id", -1)]
350+
sort = [("created_at", -1), ("source_message_id", -1)]
351351

352352
total, docs = await asyncio.gather(
353353
col.count_documents(query),

‎stream/helpers/cover_search.py‎

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from typing import Any
99
from urllib.parse import quote
1010

11-
from aiohttp import ClientSession
11+
from aiohttp import ClientSession, ClientTimeout
1212

1313
from stream.core.config_manager import Config
1414
from stream.helpers.hoaders import hoaders_big_cover_url, hoaders_cover_info
@@ -192,7 +192,7 @@ async def spotify_album_cover_url(*, artist: str, album: str, year: int | None =
192192
_dbg(f"[cover] spotify_album query={q!r}")
193193
url = f"https://api.spotify.com/v1/search?q={quote(q)}&type=album&limit=5"
194194
async with _SPOTIFY_SEM:
195-
async with ClientSession() as session:
195+
async with ClientSession(timeout=ClientTimeout(total=5.0)) as session:
196196
async with session.get(url, headers=headers) as resp:
197197
payload = await _json_or_none(resp, label="spotify_album")
198198
if payload is None:
@@ -290,7 +290,7 @@ async def spotify_best_track(*, title: str, artist: str, album: str = "", year:
290290
_dbg(f"[cover] spotify_track query={q!r}")
291291
url = f"https://api.spotify.com/v1/search?q={quote(q)}&type=track&limit=5"
292292
async with _SPOTIFY_SEM:
293-
async with ClientSession() as session:
293+
async with ClientSession(timeout=ClientTimeout(total=5.0)) as session:
294294
async with session.get(url, headers=headers) as resp:
295295
payload = await _json_or_none(resp, label="spotify_track")
296296
if payload is None:
@@ -399,6 +399,7 @@ async def apple_cover_url(*, title: str, artist: str, album: str = "", year: int
399399
best_art = None
400400
best_score = -1
401401

402+
terms = terms[:2]
402403
seen: set[str] = set()
403404
for term in terms:
404405
k = (term or "").strip().casefold()
@@ -414,8 +415,8 @@ async def apple_cover_url(*, title: str, artist: str, album: str = "", year: int
414415

415416
async with _APPLE_SEM:
416417
try:
417-
async with ClientSession() as session:
418-
async with session.get(url, timeout=8) as resp:
418+
async with ClientSession(timeout=ClientTimeout(total=5.0)) as session:
419+
async with session.get(url) as resp:
419420
if resp.status in (403, 429):
420421
_dbg(f"[cover] apple rate limited/blocked status={resp.status}, backing off for 60s")
421422
_APPLE_BACKOFF_UNTIL = time.time() + 60.0
@@ -476,6 +477,7 @@ async def deezer_cover_url(*, title: str, artist: str, album: str = "", year: in
476477
if t:
477478
queries.append(f"{t} {int(year)}".strip())
478479

480+
queries = queries[:2]
479481
seen: set[str] = set()
480482
for q in queries:
481483
k = (q or "").strip().casefold()
@@ -485,14 +487,18 @@ async def deezer_cover_url(*, title: str, artist: str, album: str = "", year: in
485487

486488
url = f"https://api.deezer.com/search?q={quote(q)}&limit=3"
487489
_dbg(f"[cover] deezer query={q!r}")
488-
async with ClientSession() as session:
489-
async with session.get(url) as resp:
490-
payload = await _json_or_none(resp, label="deezer")
491-
if payload is None:
492-
continue
493-
if resp.status != 200:
494-
_dbg(f"[cover] deezer error status={resp.status} body={str(payload)[:300]!r}")
495-
continue
490+
try:
491+
async with ClientSession(timeout=ClientTimeout(total=5.0)) as session:
492+
async with session.get(url) as resp:
493+
payload = await _json_or_none(resp, label="deezer")
494+
if payload is None:
495+
continue
496+
if resp.status != 200:
497+
_dbg(f"[cover] deezer error status={resp.status} body={str(payload)[:300]!r}")
498+
continue
499+
except Exception as e:
500+
_dbg(f"[cover] deezer request failed: {e}")
501+
continue
496502

497503
data = (payload or {}).get("data") or []
498504
if not isinstance(data, list) or not data:
@@ -1053,8 +1059,6 @@ async def youtube_cover_search(
10531059

10541060
for q in queries:
10551061
candidates = await _search_youtube_innertube(q)
1056-
if not candidates:
1057-
candidates = await asyncio.to_thread(_search_youtube_ytdlp_sync, q)
10581062

10591063
for cand in candidates:
10601064
vid = cand.get("video_id")

‎stream/helpers/hoaders.py‎

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import json
33
import re
44

5-
from aiohttp import ClientSession
5+
from aiohttp import ClientSession, ClientTimeout
66

77
from stream.core.config_manager import Config
88
from stream.helpers.logger import LOGGER
@@ -170,10 +170,14 @@ async def hoaders_search(*, artist: str, album: str, country: str = "in", source
170170
}
171171

172172
async with _SEM:
173-
async with ClientSession(headers=HEADERS) as session:
174-
async with session.post(URL, json=payload) as resp:
175-
text = await resp.text()
176-
status = int(resp.status)
173+
async with ClientSession(headers=HEADERS, timeout=ClientTimeout(total=6.0)) as session:
174+
try:
175+
async with session.post(URL, json=payload) as resp:
176+
text = await resp.text()
177+
status = int(resp.status)
178+
except Exception as e:
179+
_dbg(f"[hoaders] request failed: {e}")
180+
return [], None
177181

178182
items = _parse_ndjson(text)
179183
covers = [i for i in items if isinstance(i, dict) and i.get("type") == "cover"]

‎stream/plugins/Analyzer/mediaHelper.py‎

Lines changed: 61 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1156,18 +1156,70 @@ def _dbg(msg: str) -> None:
11561156
LOG.debug(msg)
11571157

11581158

1159+
_MEDIAINFO_AVAILABLE: bool | None = None # None = not yet checked
1160+
1161+
1162+
async def _check_mediainfo_available() -> bool:
1163+
"""One-time check whether the mediainfo binary exists and runs."""
1164+
global _MEDIAINFO_AVAILABLE
1165+
if _MEDIAINFO_AVAILABLE is not None:
1166+
return _MEDIAINFO_AVAILABLE
1167+
try:
1168+
proc = await asyncio.create_subprocess_exec(
1169+
"mediainfo", "--Version",
1170+
stdout=asyncio.subprocess.PIPE,
1171+
stderr=asyncio.subprocess.PIPE,
1172+
)
1173+
await asyncio.wait_for(proc.communicate(), timeout=5.0)
1174+
_MEDIAINFO_AVAILABLE = proc.returncode == 0
1175+
except Exception:
1176+
_MEDIAINFO_AVAILABLE = False
1177+
if not _MEDIAINFO_AVAILABLE:
1178+
LOG.warning("mediainfo binary not available — metadata extraction will be limited")
1179+
else:
1180+
LOG.info("mediainfo binary OK")
1181+
return _MEDIAINFO_AVAILABLE
1182+
1183+
11591184
async def run_mediainfo(path: str) -> str:
1160-
def _run() -> str:
1161-
proc = subprocess.run(
1162-
["mediainfo", path],
1163-
stdout=subprocess.PIPE,
1164-
stderr=subprocess.PIPE,
1165-
check=False,
1185+
if not await _check_mediainfo_available():
1186+
return ""
1187+
1188+
proc = None
1189+
try:
1190+
proc = await asyncio.create_subprocess_exec(
1191+
"mediainfo",
1192+
path,
1193+
stdout=asyncio.subprocess.PIPE,
1194+
stderr=asyncio.subprocess.PIPE,
11661195
)
1167-
data = proc.stdout or proc.stderr or b""
1196+
stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout=8.0)
1197+
data = stdout or stderr or b""
11681198
return data.decode(errors="ignore")
1169-
1170-
return await asyncio.to_thread(_run)
1199+
except asyncio.TimeoutError:
1200+
LOG.warning(f"mediainfo timed out on {path}")
1201+
if proc:
1202+
try:
1203+
proc.kill()
1204+
except ProcessLookupError:
1205+
pass
1206+
try:
1207+
await asyncio.wait_for(proc.wait(), timeout=2.0)
1208+
except asyncio.TimeoutError:
1209+
pass
1210+
return ""
1211+
except Exception as e:
1212+
LOG.warning(f"mediainfo failed on {path}: {e}")
1213+
if proc and proc.returncode is None:
1214+
try:
1215+
proc.kill()
1216+
except ProcessLookupError:
1217+
pass
1218+
try:
1219+
await asyncio.wait_for(proc.wait(), timeout=2.0)
1220+
except asyncio.TimeoutError:
1221+
pass
1222+
return ""
11711223

11721224

11731225
async def _download_partial_http(

0 commit comments

Comments
 (0)