diff --git a/flake.nix b/flake.nix index 8631652..59e73ee 100644 --- a/flake.nix +++ b/flake.nix @@ -10,30 +10,46 @@ flake-utils.lib.eachDefaultSystem (system: let pkgs = import nixpkgs { inherit system; }; - pythonEnv = pkgs.python3.withPackages (ps: with ps; [ - requests - python-dotenv - ]); + lib = pkgs.lib; + lidarr-mb-gap = pkgs.python3Packages.buildPythonApplication { + pname = "lidarr-mb-gap"; + version = "1.0.0"; + src = lib.cleanSource ./src; + format = "pyproject"; + nativeBuildInputs = with pkgs.python3Packages; [ + setuptools + ]; + propagatedBuildInputs = with pkgs.python3Packages; [ + requests + python-dotenv + ]; + }; in { devShells.default = pkgs.mkShell { - buildInputs = [ - pythonEnv + buildInputs = [ + (pkgs.python3.withPackages (ps: with ps; [ + requests + python-dotenv + ])) pkgs.black ]; shellHook = '' echo "Python environment ready!" - echo "Run: python main.py" - echo "Format code with: black main.py" + echo "Run: python src/main.py" + echo "Format code with: black src/" ''; }; - packages.default = pkgs.writeShellApplication { - name = "lidarr-musicbrainz"; - runtimeInputs = [ pythonEnv ]; - text = '' - python ${./main.py} "$@" - ''; + packages.default = lidarr-mb-gap; + packages.lidarr-mb-gap = lidarr-mb-gap; + apps.default = { + type = "app"; + program = "${lidarr-mb-gap}/bin/lidarr-mb-gap"; + }; + apps.lidarr-mb-gap = { + type = "app"; + program = "${lidarr-mb-gap}/bin/lidarr-mb-gap"; }; } ); diff --git a/src/__init__.py b/src/__init__.py new file mode 100644 index 0000000..f3ebc0d --- /dev/null +++ b/src/__init__.py @@ -0,0 +1,2 @@ +"""Lidarr to MusicBrainz Missing Albums Finder""" + diff --git a/html_report.py b/src/html_report.py similarity index 99% rename from html_report.py rename to src/html_report.py index ed08177..2ad0295 100644 --- a/html_report.py +++ b/src/html_report.py @@ -1,8 +1,11 @@ """HTML report generation for missing albums""" +import logging from html import escape from typing import Dict, List +logger = logging.getLogger(__name__) + def generate_html_report(albums_to_add: List[Dict], albums_to_update: List[Dict]): """Generate an HTML report with clickable submission links""" @@ -354,4 +357,4 @@ def generate_html_report(albums_to_add: List[Dict], albums_to_update: List[Dict] with open("missing_albums.html", "w", encoding="utf-8") as f: f.write(html_content) - print(f"šŸ“„ HTML report saved to missing_albums.html") + logger.info("HTML report saved to missing_albums.html") diff --git a/main.py b/src/main.py similarity index 87% rename from main.py rename to src/main.py index 5501cdb..7e57713 100755 --- a/main.py +++ b/src/main.py @@ -5,6 +5,7 @@ for artists monitored in Lidarr, and generate submission links. """ import json +import logging import os import sys from typing import Dict, List, Optional, Tuple @@ -17,6 +18,13 @@ from html_report import generate_html_report load_dotenv() +logging.basicConfig( + level=logging.INFO, + format="[%(levelname)s] %(message)s", + handlers=[logging.StreamHandler(sys.stdout)], +) +logger = logging.getLogger(__name__) + class LidarrClient: """Client for interacting with Lidarr API""" @@ -33,7 +41,7 @@ class LidarrClient: response.raise_for_status() return response.json() except requests.exceptions.RequestException as e: - print(f"Error fetching artists from Lidarr: {e}", file=sys.stderr) + logger.error(f"Error fetching artists from Lidarr: {e}") return [] def get_monitored_artists( @@ -254,32 +262,32 @@ def main(): MAX_ARTISTS = int(os.getenv("MAX_ARTISTS", "5")) if not LIDARR_URL: - print("Error: LIDARR_URL not set.", file=sys.stderr) + logger.error("LIDARR_URL not set") sys.exit(1) if not LIDARR_API_KEY: - print("Error: LIDARR_API_KEY not set.", file=sys.stderr) + logger.error("LIDARR_API_KEY not set") sys.exit(1) lidarr = LidarrClient(LIDARR_URL, LIDARR_API_KEY) sambl = SamblClient(SAMBL_URL) - print("Fetching monitored artists from Lidarr...") + logger.info("Fetching monitored artists from Lidarr...") artists = lidarr.get_monitored_artists(["new", "all"]) if not artists: - print("No artists found with monitorNewItems set to 'new' or 'all'") + logger.warning("No artists found with monitorNewItems set to 'new' or 'all'") return total_artists = len(artists) if MAX_ARTISTS > 0 and total_artists > MAX_ARTISTS: - print( + logger.info( f"Found {total_artists} monitored artists (limiting to {MAX_ARTISTS} for testing)" ) artists = artists[:MAX_ARTISTS] else: - print(f"Found {total_artists} monitored artists") - print("\n" + "=" * 80) + logger.info(f"Found {total_artists} monitored artists") + logger.info("=" * 80) all_albums_to_add = [] all_albums_to_update = [] @@ -289,43 +297,45 @@ def main(): artist_mbid = artist.get("foreignArtistId") or artist.get("mbid") if not artist_mbid: - print(f"\nāš ļø Skipping {artist_name} - no MusicBrainz ID found") + logger.warning(f"Skipping {artist_name} - no MusicBrainz ID found") continue - print(f"\nšŸŽµ Artist: {artist_name}") - print(f" MusicBrainz ID: {artist_mbid}") + logger.info(f"Artist: {artist_name}") + logger.info(f"MusicBrainz ID: {artist_mbid}") albums_to_add, albums_to_update = sambl.find_missing_albums( artist_mbid, artist_name ) if albums_to_add: - print(f"\n šŸ“„ Albums to ADD ({len(albums_to_add)}):") + logger.info(f"Albums to ADD ({len(albums_to_add)}):") processed = _process_albums(albums_to_add, "add") all_albums_to_add.extend(processed) - print("\n".join(map(_format_album_output, processed))) + for album_output in map(_format_album_output, processed): + logger.info(album_output) if albums_to_update: - print(f"\n šŸ”„ Albums to UPDATE ({len(albums_to_update)}):") + logger.info(f"Albums to UPDATE ({len(albums_to_update)}):") processed = _process_albums(albums_to_update, "update") all_albums_to_update.extend(processed) - print("\n".join(map(_format_album_output, processed))) + for album_output in map(_format_album_output, processed): + logger.info(album_output) if not albums_to_add and not albums_to_update: - print(f" āœ“ All albums are properly linked!") + logger.info("All albums are properly linked!") - print("\n" + "=" * 80) - print(f"\nšŸ“Š Summary:") - artists_info = f" Artists processed: {len(artists)}" + logger.info("=" * 80) + logger.info("Summary:") + artists_info = f"Artists processed: {len(artists)}" if MAX_ARTISTS > 0 and total_artists > MAX_ARTISTS: artists_info += f" (of {total_artists} total)" - print(artists_info) - print(f" Albums to ADD: {len(all_albums_to_add)}") - print(f" Albums to UPDATE: {len(all_albums_to_update)}") + logger.info(artists_info) + logger.info(f"Albums to ADD: {len(all_albums_to_add)}") + logger.info(f"Albums to UPDATE: {len(all_albums_to_update)}") all_albums = all_albums_to_add + all_albums_to_update if not all_albums: - print("\n✨ All albums are already on MusicBrainz!") + logger.info("All albums are already on MusicBrainz!") return output_data = { @@ -339,7 +349,7 @@ def main(): } with open("missing_albums.json", "w", encoding="utf-8") as f: json.dump(output_data, f, indent=2, ensure_ascii=False) - print(f"\nšŸ’¾ Results saved to missing_albums.json") + logger.info("Results saved to missing_albums.json") generate_html_report(all_albums_to_add, all_albums_to_update) diff --git a/src/pyproject.toml b/src/pyproject.toml new file mode 100644 index 0000000..7dbce20 --- /dev/null +++ b/src/pyproject.toml @@ -0,0 +1,20 @@ +[build-system] +requires = ["setuptools"] +build-backend = "setuptools.build_meta" + +[tool.setuptools] +py-modules = ["main", "html_report"] + +[project] +name = "lidarr-mb-gap" +version = "1.0.0" +description = "Lidarr to MusicBrainz Missing Albums Finder" +requires-python = ">=3.8" +dependencies = [ + "requests", + "python-dotenv", +] + +[project.scripts] +lidarr-mb-gap = "main:main" +