commit 02311a7cad9ed98a5c57c387710b48f44100cffe Author: Jamie Miller Date: Sun Sep 21 12:06:42 2025 +0000 Add .gitignore to ignore logs directory diff --git a/.env.backup b/.env.backup new file mode 100644 index 0000000..4f3554d --- /dev/null +++ b/.env.backup @@ -0,0 +1,2 @@ +SOURCE_API_URL=https://maloja.kansaigaijin.com/apis/mlj_1/scrobbles +SOURCE_API_KEY=MeEpNNVgLb9nB8OPbgtKjxn5jo1l8KLYTLyWvWBs48WLTij0BaYNSXtJY0UMa8 \ No newline at end of file diff --git a/.env.template b/.env.template new file mode 100644 index 0000000..b8cd146 --- /dev/null +++ b/.env.template @@ -0,0 +1,4 @@ +# Maloja API details +# Get these from your Maloja instance settings +SOURCE_API_URL= +SOURCE_API_KEY= \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..333c1e9 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +logs/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..8c002e4 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,20 @@ +FROM python:3.11-slim + +# Set working directory inside the container +WORKDIR /app + +# Create logs directory +RUN mkdir -p /app/logs + +# Copy requirements and install Python dependencies +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +# Copy only the required script for this service +COPY lidarr_api.py . + +# Ensure logs directory is accessible +VOLUME ["/app/logs"] + +# The CMD to run the API service +CMD ["python3", "lidarr_api.py"] \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..127f0f7 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,13 @@ +services: + lidarr-importer-api: + build: . + container_name: maloja-lidarr-importer-api + volumes: + - ./logs:/app/logs + ports: + - "5110:5000" + environment: + - SOURCE_API_URL=${SOURCE_API_URL} + - SOURCE_API_KEY=${SOURCE_API_KEY} + command: ["python3", "lidarr_api.py"] + restart: "unless-stopped" \ No newline at end of file diff --git a/lidarr_api.py b/lidarr_api.py new file mode 100644 index 0000000..3b1e940 --- /dev/null +++ b/lidarr_api.py @@ -0,0 +1,128 @@ +#!/usr/bin/env python3 +import requests +import json +import time +import logging +import sys +import os +import musicbrainzngs +from typing import List, Dict, Optional +from flask import Flask, jsonify +import threading + +# ---------------------------------------------------------------------- +# Logging +# ---------------------------------------------------------------------- +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(levelname)s - %(message)s', + handlers=[ + logging.FileHandler('/app/logs/lidarr_api.log'), + logging.StreamHandler(sys.stdout) + ] +) +logger = logging.getLogger(__name__) + +# ---------------------------------------------------------------------- +# Global state and Flask app +# ---------------------------------------------------------------------- +app = Flask(__name__) +artist_ids = [] + +# Configure MusicBrainzngs +musicbrainzngs.set_useragent( + "Maloja-Lidarr-Sync-API", + "1.0", + "your-email@example.com" +) + +# ---------------------------------------------------------------------- +# MusicBrainz API helpers +# ---------------------------------------------------------------------- +def get_artist_id_from_recording(recording_mbid: str) -> Optional[str]: + """Finds the artist MBID for a given recording MBID.""" + try: + result = musicbrainzngs.get_recording_by_id(recording_mbid, includes=["artist-credits"]) + if result.get('recording', {}).get('artist-credit', []): + artist = result['recording']['artist-credit'][0] + if artist.get('artist', {}).get('id'): + return artist['artist']['id'] + except musicbrainzngs.MusicBrainzError as e: + logger.error(f"MusicBrainz API error fetching recording {recording_mbid}: {e}") + except Exception as e: + logger.error(f"An unexpected error occurred: {e}") + return None + +def get_artist_id_by_name(artist_name: str) -> Optional[str]: + """Finds the artist MBID by searching for their name.""" + try: + result = musicbrainzngs.search_artists(artist=artist_name, limit=1) + if result.get('artist-list', []): + return result['artist-list'][0].get('id') + except musicbrainzngs.MusicBrainzError as e: + logger.error(f"MusicBrainz API error searching for artist '{artist_name}': {e}") + except Exception as e: + logger.error(f"An unexpected error occurred: {e}") + return None + +# ---------------------------------------------------------------------- +# Main logic to fetch and process artists +# ---------------------------------------------------------------------- +def fetch_and_process_artists(): + """Fetches tracks from Maloja and populates the global artist_ids list.""" + global artist_ids + while True: + try: + api_url = os.getenv("SOURCE_API_URL") + api_key = os.getenv("SOURCE_API_KEY") + + if not api_url or not api_key: + logger.error("API URL or key not set. Exiting thread.") + break + + resp = requests.get(api_url, headers={'x-api-key': api_key}, params={'key': api_key}, timeout=15) + resp.raise_for_status() + tracks_data = resp.json().get('list', []) + + unique_artist_ids = set() + for track in tracks_data: + if not isinstance(track, dict): + continue + + recording_mbid = track.get("track", {}).get("recording_mbid") + artist_name = track.get("track", {}).get("artists", [""])[0] + + if recording_mbid: + artist_id = get_artist_id_from_recording(recording_mbid) + if artist_id: + unique_artist_ids.add(artist_id) + elif artist_name: + artist_id = get_artist_id_by_name(artist_name) + if artist_id: + unique_artist_ids.add(artist_id) + + artist_ids = sorted(list(unique_artist_ids)) + logger.info(f"Artist list updated. Found {len(artist_ids)} unique artist IDs.") + except Exception as e: + logger.error(f"An error occurred in the fetch thread: {e}") + + time.sleep(3600) + +# ---------------------------------------------------------------------- +# Flask routes +# ---------------------------------------------------------------------- +@app.route('/artists') +def get_artists(): + if not artist_ids: + return jsonify([]) + + lidarr_list = [{"foreignId": artist_id} for artist_id in artist_ids] + return jsonify(lidarr_list) + +# ---------------------------------------------------------------------- +# Main execution +# ---------------------------------------------------------------------- +if __name__ == '__main__': + thread = threading.Thread(target=fetch_and_process_artists, daemon=True) + thread.start() + app.run(host='0.0.0.0', port=5000) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..509559c --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +requests==2.31.0 +musicbrainzngs==0.7.1 +Flask==2.3.3 \ No newline at end of file