Refactor flake.nix and enhance project structure

- Updated `flake.nix` to define a new Python application `lidarr-mb-gap` for identifying missing albums on MusicBrainz.
- Improved development shell environment by including a Python environment with necessary packages.
- Added new source files: `__init__.py`, `html_report.py`, and `main.py` to implement core functionality and HTML report generation.
- Introduced `pyproject.toml` for better package management and project metadata.
- Enhanced user instructions in the shell hook for running the application.
This commit is contained in:
Danilo Reyes
2025-11-11 10:42:34 -06:00
parent 20b07450d9
commit 7f6b998787
5 changed files with 90 additions and 39 deletions

View File

@@ -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
(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";
};
}
);

2
src/__init__.py Normal file
View File

@@ -0,0 +1,2 @@
"""Lidarr to MusicBrainz Missing Albums Finder"""

View File

@@ -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")

View File

@@ -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("\nAll 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)

20
src/pyproject.toml Normal file
View File

@@ -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"