Iniital Commit

This commit is contained in:
Johan Hjorth 2025-06-23 10:31:25 +02:00
parent 91245007e6
commit f20aa9b0d8
6 changed files with 168 additions and 1 deletions

View file

@ -1,2 +1,17 @@
# discoverylidarr # DiscoveryLidarr 🎧
Upptäck nya artister baserat på din Last.fm-historik och lägg till dem i Lidarr automatiskt.
## Instruktioner
1. Fyll i `config.py` med dina egna API-nycklar och inställningar.
2. Installera beroenden:
```
pip install requests
```
3. Kör scriptet:
```
python3 discovery_sync.py
```
Vill du automatisera? Lägg till i cron eller som systemd-timer.

15
config.py Normal file
View file

@ -0,0 +1,15 @@
LASTFM_USERNAME = "ditt_lastfm_namn"
LASTFM_API_KEY = "din_lastfm_api_nyckel"
LIDARR_URL = "http://localhost:8686"
LIDARR_API_KEY = "din_lidarr_api_nyckel"
ROOT_FOLDER = "/media/music2"
QUALITY_PROFILE_ID = 1
MIN_PLAYS = 15
RECENT_MONTHS = 3
MAX_SIMILAR_PER_ART = 20
SIMILAR_MATCH_MIN = 0.5
CACHE_TTL_HOURS = 24
DEBUG_PRINT = True

48
discovery_sync.py Normal file
View file

@ -0,0 +1,48 @@
from config import *
from lastfm_helpers import lf_request, recent_artists
from lidarr_helpers import lidarr_api_add_artist
from musicbrainz_helpers import load_cache, save_cache
import time, logging
logging.basicConfig(level=logging.INFO)
log = logging.getLogger("DiscoveryLidarr")
def sync():
start = time.time()
cache = load_cache()
added_artists = set(cache.get("added_artists", []))
similar_cache = cache.setdefault("similar_cache", {})
recent = recent_artists()
log.info(f"🎧 Analyserar {len(recent)} senaste artister från Last.fm")
new = 0
for name, mbid in recent:
if not mbid or mbid in added_artists:
continue
if mbid in similar_cache:
sims = similar_cache[mbid]["data"]
else:
js = lf_request("artist.getSimilar", mbid=mbid, limit=MAX_SIMILAR_PER_ART)
sims = js.get("similarartists", {}).get("artist", []) if js else []
similar_cache[mbid] = {"ts": time.time(), "data": sims}
save_cache(cache)
for sim in sims:
sid = sim.get("mbid")
match = float(sim.get("match", 0))
if not sid or sid in added_artists or match < SIMILAR_MATCH_MIN:
continue
log.info(f"✨ Ny artist: {sim.get('name')} (match {match:.2f})")
if lidarr_api_add_artist(sid):
added_artists.add(sid)
new += 1
cache["added_artists"] = list(added_artists)
save_cache(cache)
log.info(f"✅ Klar! {new} nya artister tillagda på {((time.time()-start)/60):.1f} min")
if __name__ == "__main__":
sync()

43
lastfm_helpers.py Normal file
View file

@ -0,0 +1,43 @@
from config import *
import requests, time, urllib.parse
from collections import defaultdict
from datetime import datetime, timedelta
def lf_request(method, **params):
url = "https://ws.audioscrobbler.com/2.0/"
params.update({
"method": method,
"api_key": LASTFM_API_KEY,
"format": "json"
})
try:
r = requests.get(url, params=params)
r.raise_for_status()
return r.json()
except Exception as e:
print(f"[LFM ERR] {e}")
return None
def recent_artists():
since = int((datetime.utcnow() - timedelta(days=30 * RECENT_MONTHS)).timestamp())
counts = defaultdict(int)
page = 1
while True:
js = lf_request(
"user.getRecentTracks", user=LASTFM_USERNAME,
limit=200, page=page, from_=since
)
if not js:
break
for t in js.get("recenttracks", {}).get("track", []):
a = t["artist"]
counts[(a["#text"], a.get("mbid", ""))] += 1
attr = js.get("recenttracks", {}).get("@attr", {})
if page >= int(attr.get("totalPages", 1)):
break
page += 1
return [(n, m) for (n, m), c in counts.items() if c >= MIN_PLAYS]

28
lidarr_helpers.py Normal file
View file

@ -0,0 +1,28 @@
from config import *
import requests
def lidarr_api_add_artist(mbid):
lookup_url = f"{LIDARR_URL}/api/v1/artist/lookup?term=mbid:{mbid}"
headers = {"X-Api-Key": LIDARR_API_KEY, "Content-Type": "application/json"}
res = requests.get(lookup_url, headers=headers)
if not res.ok or not res.json():
return False
artist = res.json()[0]
payload = {
"foreignArtistId": artist['foreignArtistId'],
"artistName": artist['artistName'],
"monitored": True,
"qualityProfileId": QUALITY_PROFILE_ID,
"metadataProfileId": 1,
"rootFolderPath": ROOT_FOLDER,
"addOptions": {
"monitor": "all",
"searchForMissingAlbums": True
}
}
add_url = f"{LIDARR_URL}/api/v1/artist"
post_res = requests.post(add_url, headers=headers, json=payload)
return post_res.ok

18
musicbrainz_helpers.py Normal file
View file

@ -0,0 +1,18 @@
import json
from pathlib import Path
CACHE_FILE = Path(__file__).resolve().parent / "cache.json"
def load_cache():
try:
if not CACHE_FILE.exists():
with open(CACHE_FILE, "w") as f:
json.dump({"added_artists": [], "similar_cache": {}}, f)
with open(CACHE_FILE, "r") as f:
return json.load(f)
except Exception:
return {"added_artists": [], "similar_cache": {}}
def save_cache(cache):
with open(CACHE_FILE, "w") as f:
json.dump(cache, f, indent=2)