Merge remote-tracking branch 'refs/remotes/origin/main'
This commit is contained in:
commit
d56dbb3d9f
5 changed files with 78 additions and 33 deletions
|
@ -1,7 +1,9 @@
|
||||||
import dotenv
|
import dotenv
|
||||||
|
from connect import get_connection
|
||||||
from flask import Flask, Response, jsonify, request
|
from flask import Flask, Response, jsonify, request
|
||||||
from flask_cors import CORS
|
from flask_cors import CORS
|
||||||
from flask_socketio import SocketIO, emit
|
from flask_socketio import SocketIO, emit
|
||||||
|
from state import State
|
||||||
|
|
||||||
from .room import Room
|
from .room import Room
|
||||||
from .song import Song, init_db, get_song_by_title_artist, add_song_in_db
|
from .song import Song, init_db, get_song_by_title_artist, add_song_in_db
|
||||||
|
@ -15,8 +17,9 @@ app.config["SECRET_KEY"] = "your_secret_key"
|
||||||
socketio = SocketIO(app)
|
socketio = SocketIO(app)
|
||||||
CORS(app)
|
CORS(app)
|
||||||
|
|
||||||
|
db_conn = get_connection()
|
||||||
ROOMS: dict[int, Room] = {} # { room_id: room, ... }
|
state = State(app, db_conn.cursor())
|
||||||
|
init_db(state.db)
|
||||||
|
|
||||||
|
|
||||||
def error(msg: str, status: int = 400) -> Response:
|
def error(msg: str, status: int = 400) -> Response:
|
||||||
|
@ -33,7 +36,7 @@ def join():
|
||||||
if room_id is None:
|
if room_id is None:
|
||||||
return error("Missing room id")
|
return error("Missing room id")
|
||||||
|
|
||||||
if (room := ROOMS.get(int(room_id))) is None:
|
if (room := state.rooms.get(int(room_id))) is None:
|
||||||
return error("Invalid room")
|
return error("Invalid room")
|
||||||
|
|
||||||
if room.pin is not None and room.pin != code:
|
if room.pin is not None and room.pin != code:
|
||||||
|
@ -47,7 +50,7 @@ def queue():
|
||||||
if (room_id := request.args.get("room")) is None:
|
if (room_id := request.args.get("room")) is None:
|
||||||
return error("Missing room id")
|
return error("Missing room id")
|
||||||
|
|
||||||
if (room := ROOMS.get(int(room_id))) is None:
|
if (room := state.rooms.get(int(room_id))) is None:
|
||||||
return error("Invalid room")
|
return error("Invalid room")
|
||||||
|
|
||||||
return {"success": True, "queue": room.playing}
|
return {"success": True, "queue": room.playing}
|
||||||
|
@ -58,7 +61,7 @@ def queue_next():
|
||||||
if (room_id := request.args.get("room")) is None:
|
if (room_id := request.args.get("room")) is None:
|
||||||
return error("Missing room id")
|
return error("Missing room id")
|
||||||
|
|
||||||
if (room := ROOMS.get(int(room_id))) is None:
|
if (room := state.rooms.get(int(room_id))) is None:
|
||||||
return error("Invalid room")
|
return error("Invalid room")
|
||||||
|
|
||||||
room.playing_idx += 1
|
room.playing_idx += 1
|
||||||
|
@ -90,7 +93,7 @@ def room_new():
|
||||||
lat, lon = room_cords.split(",")
|
lat, lon = room_cords.split(",")
|
||||||
|
|
||||||
room = Room(
|
room = Room(
|
||||||
id=max(ROOMS or [0]) + 1, #
|
id=max(state.rooms or [0]) + 1, #
|
||||||
coord=(float(lat), float(lon)),
|
coord=(float(lat), float(lon)),
|
||||||
name=room_name,
|
name=room_name,
|
||||||
pin=room_pin,
|
pin=room_pin,
|
||||||
|
@ -101,7 +104,7 @@ def room_new():
|
||||||
playing_idx=-1,
|
playing_idx=-1,
|
||||||
)
|
)
|
||||||
|
|
||||||
ROOMS[room.id] = room
|
state.rooms[room.id] = room
|
||||||
|
|
||||||
return {"success": True, "room_id": room.id}
|
return {"success": True, "room_id": room.id}
|
||||||
|
|
||||||
|
@ -115,7 +118,7 @@ def room():
|
||||||
"private": room.pin is not None,
|
"private": room.pin is not None,
|
||||||
"coords": room.coord,
|
"coords": room.coord,
|
||||||
}
|
}
|
||||||
for room in ROOMS.values()
|
for room in state.rooms.values()
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -124,7 +127,7 @@ def add_song():
|
||||||
if (room_id := request.args.get("room")) is None:
|
if (room_id := request.args.get("room")) is None:
|
||||||
return error("Missing room id")
|
return error("Missing room id")
|
||||||
|
|
||||||
if (room := ROOMS.get(int(room_id))) is None:
|
if (room := state.rooms.get(int(room_id))) is None:
|
||||||
return error("Invalid room")
|
return error("Invalid room")
|
||||||
|
|
||||||
if (query := request.args.get("query")) is None:
|
if (query := request.args.get("query")) is None:
|
||||||
|
@ -135,7 +138,7 @@ def add_song():
|
||||||
if (song := get_song_by_title_artist(info.title, info.artist)) is None:
|
if (song := get_song_by_title_artist(info.title, info.artist)) is None:
|
||||||
res = download_song_mp3(info.title, info.artist)
|
res = download_song_mp3(info.title, info.artist)
|
||||||
if res is None:
|
if res is None:
|
||||||
ops
|
return error("Cannot get info from YT")
|
||||||
yt_id, _ = res
|
yt_id, _ = res
|
||||||
## song not found, downolad from YT
|
## song not found, downolad from YT
|
||||||
## add in DB
|
## add in DB
|
||||||
|
|
|
@ -4,4 +4,5 @@ import sqlite3
|
||||||
def get_connection():
|
def get_connection():
|
||||||
conn = sqlite3.connect(".data/jukebox.db")
|
conn = sqlite3.connect(".data/jukebox.db")
|
||||||
conn.row_factory = sqlite3.Row
|
conn.row_factory = sqlite3.Row
|
||||||
|
conn.autocommit = True
|
||||||
return conn
|
return conn
|
||||||
|
|
|
@ -9,6 +9,7 @@ TAG_WEIGHT = 0.1
|
||||||
RANDOM_WEIGHT = 0.1
|
RANDOM_WEIGHT = 0.1
|
||||||
RECENT_PENALTY = 0.5
|
RECENT_PENALTY = 0.5
|
||||||
RECENT_COUNT = 10
|
RECENT_COUNT = 10
|
||||||
|
QUEUE_SIZE = 3
|
||||||
|
|
||||||
|
|
||||||
type UserScoredSong = tuple[Song, int]
|
type UserScoredSong = tuple[Song, int]
|
||||||
|
@ -34,11 +35,26 @@ class Room:
|
||||||
pin: int | None
|
pin: int | None
|
||||||
tags: set[str]
|
tags: set[str]
|
||||||
|
|
||||||
songs: dict[str, UserScoredSong] # canzoni + voto
|
songs: dict[str, UserScoredSong] # all songs + user score (the playlist)
|
||||||
history: list[Song] # canzoni riprodotte (in ordine)
|
history: list[Song] # all songs previously played
|
||||||
playing: list[Song] # canzoni che sono i riproduzione
|
|
||||||
|
playing: list[Song] # queue
|
||||||
playing_idx: int
|
playing_idx: int
|
||||||
|
|
||||||
|
def renew_queue(self):
|
||||||
|
for song in self.playing:
|
||||||
|
self.history.append(song)
|
||||||
|
self.playing.clear()
|
||||||
|
self.playing_idx = 0
|
||||||
|
|
||||||
|
rankings: dict[str, float] = {}
|
||||||
|
for id, (song, user_score) in self.songs.items():
|
||||||
|
rankings[id] = self.rank_song(song, user_score).total()
|
||||||
|
|
||||||
|
# sort dict items by their values and pick the highest 3
|
||||||
|
top_items = sorted(rankings.items(), key=lambda item: item[1], reverse=True)[:QUEUE_SIZE] # [:3]
|
||||||
|
self.playing = list(map(lambda x: self.songs[x[0]][0], top_items)) # remove the ranking and take only the songs
|
||||||
|
|
||||||
def rank_song_from_id(self, id: str) -> Rank:
|
def rank_song_from_id(self, id: str) -> Rank:
|
||||||
scored = self.songs[id]
|
scored = self.songs[id]
|
||||||
return self.rank_song(scored[0], scored[1])
|
return self.rank_song(scored[0], scored[1])
|
||||||
|
@ -109,18 +125,18 @@ def test_algo():
|
||||||
None,
|
None,
|
||||||
set(["rock", "rap"]),
|
set(["rock", "rap"]),
|
||||||
{
|
{
|
||||||
"paulham": (songs[0], 2),
|
"paulham": (songs[0], 7),
|
||||||
"cisco": (songs[1], 1),
|
"cisco": (songs[1], 5),
|
||||||
"vpn": (songs[2], 5),
|
"vpn": (songs[2], 11),
|
||||||
"gang": (songs[3], 1),
|
"gang": (songs[3], 10),
|
||||||
"bertha1": (songs[4], 1),
|
"bertha1": (songs[4], 4),
|
||||||
"bertha2": (songs[5], 1),
|
"bertha2": (songs[5], 5),
|
||||||
"bertha3": (songs[6], 1),
|
"bertha3": (songs[6], -4),
|
||||||
"cexx": (songs[7], -123123),
|
"cexx": (songs[7], 12),
|
||||||
},
|
},
|
||||||
[songs[4], songs[5], songs[0]],
|
[],
|
||||||
|
[songs[2], songs[0], songs[1]],
|
||||||
|
1,
|
||||||
)
|
)
|
||||||
|
room.renew_queue()
|
||||||
print(room.rank_song_from_id("paulham"), room.rank_song_from_id("paulham").total())
|
print(room.playing)
|
||||||
print(room.rank_song_from_id("vpn"), room.rank_song_from_id("vpn").total())
|
|
||||||
print(room.rank_song_from_id("cexx"), room.rank_song_from_id("cexx").total())
|
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
import uuid
|
import uuid
|
||||||
|
from sqlite3 import Cursor
|
||||||
|
|
||||||
from .connect import get_connection
|
from .connect import get_connection
|
||||||
|
|
||||||
|
|
||||||
def init_db():
|
def init_db(db: Cursor):
|
||||||
conn = get_connection()
|
db.execute("""
|
||||||
cursor = conn.cursor()
|
|
||||||
cursor.execute("""
|
|
||||||
CREATE TABLE IF NOT EXISTS songs (
|
CREATE TABLE IF NOT EXISTS songs (
|
||||||
mbid TEXT PRIMARY KEY,
|
mbid TEXT PRIMARY KEY,
|
||||||
title TEXT NOT NULL,
|
title TEXT NOT NULL,
|
||||||
|
@ -16,11 +16,9 @@ def init_db():
|
||||||
youtube_id TEXT NOT NULL
|
youtube_id TEXT NOT NULL
|
||||||
);
|
);
|
||||||
""")
|
""")
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass(frozen=True)
|
||||||
class Song:
|
class Song:
|
||||||
mbid: str
|
mbid: str
|
||||||
title: str
|
title: str
|
||||||
|
@ -44,6 +42,20 @@ def get_song_by_mbid(mbid: str) -> Song | None:
|
||||||
return song
|
return song
|
||||||
|
|
||||||
|
|
||||||
|
def get_song_by_title_artist(title: str, artist: str) -> Song | None:
|
||||||
|
conn = get_connection()
|
||||||
|
cursor = conn.cursor()
|
||||||
|
cursor.execute("SELECT * FROM songs WHERE title = ? AND artist = ?", (title, artist))
|
||||||
|
row = cursor.fetchone()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
if row is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
song = Song(mbid=row["mbid"], title=row["title"], artist=row["artist"], tags=row["tags"].split(","), image_id=row["lastfm_image_id"], youtube_id=row["youtube_id"])
|
||||||
|
return song
|
||||||
|
|
||||||
|
|
||||||
def add_song_in_db(title: str, artist: str, tags: list[str], image_id: str, yt_id: str) -> None:
|
def add_song_in_db(title: str, artist: str, tags: list[str], image_id: str, yt_id: str) -> None:
|
||||||
conn = get_connection()
|
conn = get_connection()
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
|
13
backend/src/state.py
Normal file
13
backend/src/state.py
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from sqlite3 import Cursor
|
||||||
|
|
||||||
|
from flask import Flask
|
||||||
|
|
||||||
|
from .room import Room
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class State:
|
||||||
|
app: Flask
|
||||||
|
db: Cursor
|
||||||
|
rooms: dict[int, Room] = {} # { room_id: room, ... }
|
Loading…
Add table
Add a link
Reference in a new issue