feat: Phase 3 AI Radio (DJ Host & interleaved playlist) and Like/Dislike/Delete actions

- Add Like button: promotes wave track to permanent library and refills buffer
- Add Dislike button: blacklists track in SQLite disliked_tracks, removes audio, refills buffer
- Add Delete button in history table for library file removal
- Add AI Radio module: wttr.in weather, Qwen 2.5 host monologues, speech synthesis, ID3 tags
- Generate interleaved 'AI Радио.m3u8' alongside clean 'Моя Волна.m3u8'
- Update Web UI with Radio DJ card, voice selection, and track actions
This commit is contained in:
Antigravity committed 2026-10-12 16:34:46 +03:00
1 parent 3db9bdb38b
commit e5afeb06cc
15 files changed
+945 -8

No files matched your search

+47
View File
@@ -44,6 +44,17 @@ def init_db():
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
""")
# Disliked / Blacklisted tracks table
cursor.execute("""
CREATE TABLE IF NOT EXISTS disliked_tracks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
artist TEXT NOT NULL,
title TEXT NOT NULL,
disliked_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE(artist, title)
)
""")
conn.commit()
conn.close()
@@ -66,6 +77,42 @@ def record_downloaded_track(service: str, service_id: str, artist: str, title: s
conn.commit()
conn.close()
def remove_downloaded_track(file_path: str):
conn = get_db()
cursor = conn.cursor()
cursor.execute("DELETE FROM downloaded_tracks WHERE file_path = ?", (file_path,))
conn.commit()
conn.close()
def add_disliked_track(artist: str, title: str):
conn = get_db()
cursor = conn.cursor()
cursor.execute("""
INSERT OR IGNORE INTO disliked_tracks (artist, title)
VALUES (?, ?)
""", (artist.strip(), title.strip()))
conn.commit()
conn.close()
def get_disliked_track_keys() -> set:
import re
def _norm(s: str) -> str:
s = s.lower()
s = re.sub(r"[^\w\s]", "", s)
return " ".join(s.split())
conn = get_db()
cursor = conn.cursor()
cursor.execute("SELECT artist, title FROM disliked_tracks")
keys = set()
for r in cursor.fetchall():
art = _norm(r[0])
tit = _norm(r[1])
if art and tit:
keys.add(f"{art}|{tit}")
conn.close()
return keys
def get_recent_tracks(limit: int = 50) -> List[Dict[str, Any]]:
conn = get_db()
cursor = conn.cursor()