added a bunch of stuff

This commit is contained in:
JISAUAY 2025-04-22 16:35:48 -05:00
parent 5aaf6ede5d
commit 95c4dc96f6
26 changed files with 362 additions and 27 deletions

60
ListenBrainz.py Normal file
View File

@ -0,0 +1,60 @@
from dataclasses import dataclass
@dataclass
class ListenBrainzScrobble:
artist_name: str
release_name: str
track_name: str
# Optional
artist_mbids: list[str] | None = None
release_group_mbid: str | None = None
release_mbid: str | None = None
recording_mbid: str | None = None
track_mbid: str | None = None
work_mbids: list[str] | None = None
tracknumber: str | None = None
isrc: str | None = None
spotify_id: str | None = None
tags: list[str] | None = None
media_player: str | None = None
media_player_version: str | None = None
submission_client: str | None = None
submission_client_version: str | None = None
music_service: str | None = None
music_service_name: str | None = None
origin_url: str | None = None
duration: int | None = None
duration_ms: int | None = None
@staticmethod
def from_json(data: dict):
additional_info_keys = [
'artist_mbids',
'release_group_mbid',
'release_mbid',
'recording_mbid',
'track_mbid',
'work_mbids',
'tracknumber',
'isrc',
'spotify_id',
'tags',
'media_player',
'media_player_version',
'submission_client',
'submission_client_version',
'music_service',
'music_service_name',
'origin_url',
'duration',
'duration_ms'
]
add_info = data['additional_info']
return ListenBrainzScrobble(
artist_name=data['artist_name'],
release_name=data['release_name'],
track_name=data['track_name'],
**{key:(add_info[key] if key in add_info else None) for key in additional_info_keys}
)

162
MusicDatabase.py Normal file
View File

@ -0,0 +1,162 @@
import datetime
import sqlite3
import logging
import os
from dataclasses import dataclass
from typing import Self
DATABASE = "music-history.db"
@dataclass
class Album:
id: int
name: str
mbid: str | None = None
cover_art_url: str | None = None
@classmethod
def from_row(cls, row: tuple[int, str, str | None, str | None]) -> Self:
return cls(*row)
class Artist:
pass
@dataclass
class Song:
id: int
name: str
length: int
album_id: int
artist_ids: list[int]
mbid: str | None = None # Music Brainz Id
@classmethod
def from_rows(cls, rows: list[tuple[int, str, int, int, int, str | None]]) -> Self:
artist_ids: list[int] = [row[4] for row in rows]
first_row = rows[0]
return cls(
first_row[0],
first_row[1],
first_row[2],
first_row[3],
artist_ids,
first_row[5]
)
def __str__(self) -> str:
return f'Title: {self.name}, Length: {self.length}, Album Id: {self.album_id}, Artist Ids: {self.artist_ids}, MBID: {self.mbid}'
class Connect:
def __init__(self, database: str):
self.con: sqlite3.Connection = sqlite3.connect(database)
def __enter__(self) -> tuple[sqlite3.Connection, sqlite3.Cursor]:
return self.con, self.con.cursor()
def __exit__(self, type, value, traceback):
self.con.commit()
self.con.close()
# Takes a path to a .sql file and returns it's contents
def _load_query(path: str) -> str:
return open(path, 'r').read()
def execute_query(query_path: str, qargs: list | tuple) -> list:
if not os.path.exists(query_path):
logging.error(f'Query Path "{query_path}" does not exist')
return []
query: str = _load_query(query_path)
with Connect(DATABASE) as (conn, cur):
cur.execute(query, qargs)
return cur.fetchall()
def insert_listen_event(song_id: int, user_id: int, date: int | datetime.datetime, time: int):
pass
def search_song_mbid(mbid: str) -> Song | None:
results: list = execute_query('sql/get_song_by_mbid.sql', (mbid,))
if results:
return Song.from_rows(results)
return None
def search_song_name(name: str) -> list[Song] | None:
results: list = execute_query('sql/get_song_by_name.sql', (name,))
if not results:
return None
songs: list[Song] = []
current_song_id: int | None = None
rows = []
for row in results:
if not current_song_id:
current_song_id = row[0]
rows.append(row)
elif row[0] != current_song_id:
songs.append(Song.from_rows(rows))
current_song_id = row[0]
rows = [row]
else:
rows.append(row)
return songs
def search_song_by_id(id: int) -> Song | None:
results: list = execute_query('sql/get_song_by_id.sql', (id,))
if results:
return Song.from_rows(results)
return None
def insert_song(id: int, name: str, length: int, album_id: int, artist_id: int, mbid: str | None = None) -> None:
execute_query('sql/insert_song.sql', (id, name, length, album_id, artist_id, mbid))
def get_last_song_id() -> int:
return execute_query('sql/get_last_song_id.sql', [])[0][0]
def search_album_by_name(name: str) -> list[Album] | None:
results: list = execute_query('sql/get_album_by_name.sql', (name,))
if not results:
return None
albums: list[Album] = []
for row in results:
albums.append(Album.from_row(row))
def search_album_by_mbid(mbid: str) -> Album | None:
pass
def search_album_by_id(id: int) -> Album | None:
pass
def insert_album(name: str, mbid: str | None, cover_art_url: str | None) -> None:
pass
def search_artist_by_name(name: str) -> list[Artist] | None:
pass
def search_artist_by_mbid(mbid: str) -> Artist | None:
pass
def search_artist_by_id(id: int) -> Artist | None:
pass
def insert_artist(name: str, mbid: str | None = None, icon_url: str | None = None) -> None:
pass

View File

@ -477,3 +477,13 @@ class SubpyConn:
response = response.json()['subsonic-response']
return response
def getAlbum(self, album_id: str) -> dict:
url = self.base_url + '/rest/getAlbum?'
payload = self._getBasePayload()
payload['id'] = album_id
response = requests.get(url + self._encodePayload(payload))
return response.json()['subsonic-response']

View File

@ -19,6 +19,12 @@ def main():
print(json.dumps(now_playing, indent=4))
# album_id = now_playing['nowPlaying']['entry'][0]['albumId']
# album_info = conn.getAlbum(album_id)
# print(json.dumps(album_info, indent=4))
artist_info = conn.getArtist(now_playing['nowPlaying']['entry'][0]['artistId'], raw_json=True)
print(json.dumps(artist_info, indent=4))

Binary file not shown.

10
readme.md Normal file
View File

@ -0,0 +1,10 @@
# Config File Format
.ini file
```ini
[Navidrome]
user = username
pass = password
url = https://navidrome-subdomain.my-domain.tld
```

71
scrobble.py Normal file
View File

@ -0,0 +1,71 @@
import logging
import json
import re
import musicbrainzngs
import flask
import ListenBrainz
logging.basicConfig(
format="%(levelname)s %(module)s::%(filename)s::%(funcName)s %(asctime)s - %(message)s",
level=logging.DEBUG
)
app = flask.Flask(__name__)
musicbrainzngs.set_useragent("Navidrome Scrobbler", "beta 0.0")
# Returns a tuple of (track_mbid, album_mbid, artist_mbids: list)
# TODO improve matching instead of assuming the top result is correct
def get_mbid(scrobble: ListenBrainz.ListenBrainzScrobble) -> tuple[str, str, list[str]]:
optional_args = {}
if scrobble.duration:
optional_args['dur'] = scrobble.duration * 1000
elif scrobble.duration_ms:
optional_args['dur'] = scrobble.duration_ms
result = musicbrainzngs.search_recordings(f"{scrobble.track_name} {scrobble.release_name}", artist=scrobble.artist_name, **optional_args)
top_result: dict = result['recording-list'][0]
track_mbid: str = top_result['id']
album_mbid: str = top_result['release-list'][0]['id']
artist_mbids: list[str] = [artist['artist']['id'] for artist in top_result['artist-credit']]
return (track_mbid, album_mbid, artist_mbids)
# See https://listenbrainz.readthedocs.io/en/latest/users/json.html#json-doc
@app.route('/1/submit-listens', methods=["POST"])
def submit_listens():
if 'Authorization' not in flask.request.headers:
return '', 401
user_token_match: re.Match[str] | None = re.match(r'Token (.*)', flask.request.headers['Authorization'])
if not user_token_match:
return '', 401
user_token: str = user_token_match.group(1)
request_json: dict | None = flask.request.json
if not request_json:
return '', 401
listen_type: str = request_json['listen_type']
payload: list[dict] = request_json['payload']
logging.debug("User Token", user_token)
for scrobble_data in payload:
logging.debug("Recievied scrobble", json.dumps(scrobble_data, indent=4))
scrobble = ListenBrainz.ListenBrainzScrobble.from_json(scrobble_data)
return '', 200
if __name__ == '__main__':
app.run('127.0.0.1', port=8545)

1
sql/get_album.sql Normal file
View File

@ -0,0 +1 @@
SELECT * FROM albums WHERE id = ?;

View File

@ -0,0 +1 @@
SELECT * FROM albums WHERE name = ?;

View File

@ -1,3 +0,0 @@
SELECT *
FROM albums
WHERE name = ?;

1
sql/get_artist.sql Normal file
View File

@ -0,0 +1 @@
SELECT * FROM artists WHERE id = ?;

View File

@ -1,3 +0,0 @@
SELECT *
FROM artists
WHERE name = ?;

1
sql/get_last_song_id.sql Normal file
View File

@ -0,0 +1 @@
SELECT id FROM songs ORDER BY id DESC LIMIT 1;

View File

@ -1,4 +0,0 @@
SELECT *
FROM songs
ORDER BY id
DESC LIMIT 1;

1
sql/get_song_by_id.sql Normal file
View File

@ -0,0 +1 @@
SELECT * FROM songs WHERE id = ?;

1
sql/get_song_by_mbid.sql Normal file
View File

@ -0,0 +1 @@
SELECT * FROM songs WHERE musicBrainzId = ?;

1
sql/get_song_by_name.sql Normal file
View File

@ -0,0 +1 @@
SELECT * FROM songs WHERE name = ? ORDER BY id ASC;

View File

@ -1,4 +0,0 @@
SELECT *
FROM songs
WHERE name = ?
AND artist = ?;

View File

@ -1,3 +0,0 @@
SELECT *
FROM users
WHERE name = ?;

View File

@ -1,3 +1 @@
INSERT INTO
albums (name, spotify_id, cover_art_url)
VALUES (?, ?, ?);
INSERT INTO albums (id, name, musicBrainzId, cover_art_url) VALUES (?, ?, ?, ?);

View File

@ -1,3 +1 @@
INSERT INTO
artists (name, spotify_id, icon_url)
VALUES (?, ?, ?);
INSERT INTO artists (id, name, musicBrainzId, icon_url) VALUES (?, ?, ?, ?);

View File

@ -0,0 +1 @@
INSERT INTO "listen-events" (sond_id, user_id, date, time) VALUES (?, ?, ?, ?);

View File

@ -1,4 +1 @@
-- With Spotify Id
INSERT INTO
songs (id, name, length, album, artist, spotify_id)
VALUES (?, ?, ?, ?, ?, ?);
INSERT INTO songs (id, name, length, album_id, artist_id, musicBrainzId) VALUES (?, ?, ?, ?, ?, ?);

11
test-database.py Normal file
View File

@ -0,0 +1,11 @@
import MusicDatabase
print(MusicDatabase.search_song_by_id(1))
print(MusicDatabase.get_last_song_id())

10
test.py Normal file
View File

@ -0,0 +1,10 @@
import sqlite3
import json
import musicbrainzngs
musicbrainzngs.set_useragent("spotify matcher", "beta 0.0")
result = musicbrainzngs.search_recordings("Misao pandemic something worth doing!", artist="psiangel", dur="179367")
print(json.dumps(result['recording-list'][0], indent=4))

11
test2.py Normal file
View File

@ -0,0 +1,11 @@
args = {
"foo" : 1,
"bar" : 2
}
def baz(a, b, buz = None, foo = None, bar = None):
print(foo, bar)
baz(0,0, **args)