diff --git a/mbz_rss_service/config.py b/mbz_rss_service/config.py index c29cac0..c9efed6 100644 --- a/mbz_rss_service/config.py +++ b/mbz_rss_service/config.py @@ -68,10 +68,13 @@ class Config: backup_path = f"{file_path}.{datetime.now().strftime('%Y%m%d%H%M%S')}.bak" shutil.copy(file_path, backup_path) + logger.debug(f"Saving yaml to {file_path}") os.makedirs(os.path.dirname(file_path), exist_ok=True) with open(file_path, 'w') as f: yaml.dump(data, f, default_flow_style=False, sort_keys=False) + logger.debug(f"Saved yaml to {file_path}") + @property def feeds(self): return self._feeds_data.get('feeds', []) @@ -105,13 +108,16 @@ class Config: self._feeds_data['feeds'] = [feed for feed in self.feeds if feed['id'] != feed_id] self.save_feeds() - def add_artist_to_feed(self, feed_id, artist_id, artist_name): + def add_artist_to_feed(self, feed_id, artist_id, artist_name, links = None): for feed in self.feeds: if feed['id'] == feed_id: if 'artists' not in feed: feed['artists'] = [] if not any(a['id'] == artist_id for a in feed['artists']): - feed['artists'].append({'id': artist_id, 'name': artist_name}) + artist_data = {'id': artist_id, 'name': artist_name} + if links: + artist_data['links'] = links + feed['artists'].append(artist_data) feed['updated_at'] = datetime.now(timezone.utc).isoformat() self.save_feeds() break diff --git a/mbz_rss_service/main.py b/mbz_rss_service/main.py index dedbca8..aa1e53b 100644 --- a/mbz_rss_service/main.py +++ b/mbz_rss_service/main.py @@ -9,6 +9,8 @@ import os import sys from . import musicbrainz +XML_CONTENT_TYPE = "application/xml" + log_level_str = os.environ.get('LOG_LEVEL', 'INFO').upper() log_level = getattr(logging, log_level_str, logging.INFO) log_file = os.path.expandvars(os.environ.get('LOG_FILE', '/var/mbz-rss-feeder/log/mbz-rss-feeder.log')) @@ -154,7 +156,7 @@ def _check_cache(feed_id): try: with open(cache_file, 'r') as f: logger.debug(f"Serving cached feed for {feed_id}") - return f.read(), {"Content-Type": "application/xml"} + return f.read(), {"Content-Type": XML_CONTENT_TYPE} except IOError as e: logger.warning(f"Could not read cache file {cache_file}: {e}") @@ -195,7 +197,8 @@ def add_artist(feed_id): artist_name = request.form.get('artist_name') logger.debug(f"Request to add artist '{artist_name}' ({artist_id}) to feed {feed_id}") if artist_id and artist_name: - config.add_artist_to_feed(feed_id, artist_id, artist_name) + meta = musicbrainz.get_artist_meta_by_id(artist_id) + config.add_artist_to_feed(feed_id, artist_id, artist_name, meta['links']) return redirect(url_for('edit_feed', feed_id=feed_id)) @app.route("/feed//artist//remove", methods=["POST"]) @@ -228,7 +231,7 @@ def opml(): logger.debug("Request for OPML file") feeds = config.feeds opml_content = render_template('opml.xml', feeds=feeds) - return opml_content, {"Content-Type": "application/xml"} + return opml_content, {"Content-Type": XML_CONTENT_TYPE} @app.route('/feed/') def get_feed_rss(feed_id): @@ -277,7 +280,7 @@ def get_feed_rss(feed_id): except IOError as e: logger.warning(f"Could not write feed '{feed_data['name']}' to cache file {cache_file}: {e}") - return rss_content, {"Content-Type": "application/xml"} + return rss_content, {"Content-Type": XML_CONTENT_TYPE} @app.route("/health") def health_check(): diff --git a/mzb_rss_service/musicbrainz.py b/mbz_rss_service/musicbrainz.py similarity index 63% rename from mzb_rss_service/musicbrainz.py rename to mbz_rss_service/musicbrainz.py index 03e920c..3dff102 100644 --- a/mzb_rss_service/musicbrainz.py +++ b/mbz_rss_service/musicbrainz.py @@ -1,6 +1,6 @@ +import re import musicbrainzngs import logging -import os from .config import config from datetime import datetime from email.utils import formatdate @@ -62,6 +62,10 @@ def _process_release(release, artist_id): release_date = release.get('date', 'Unknown') _, pub_date = _parse_release_date(release_date, release['title']) + links = {} + if 'url-relation-list' in release: + links = get_artist_relation_links(release['url-relation-list']) + return { 'artist': { 'name': config.get_artist_name(artist_id), @@ -72,8 +76,54 @@ def _process_release(release, artist_id): 'title': release['title'], 'date': release_date, 'pub_date': pub_date, - 'release-group': release.get('release-group', {}) + 'release-group': release.get('release-group', {}), + 'links': links, } +def get_artist_meta_by_id(artist_id): + """get additional meta data about an artist""" + meta = { + 'links': {} + } + try: + logger.debug(f"Fetching artist {artist_id} meta data") + # Fetch artist data including URL relations + result = musicbrainzngs.get_artist_by_id(artist_id, includes=["url-rels"]) + artist_data = result.get('artist', {}) + + if 'url-relation-list' in artist_data: + meta['links'] = get_artist_relation_links(artist_data['url-relation-list']) + else: + meta['links'] = None + + return meta + + except musicbrainzngs.WebServiceError as exc: + logger.error(f"Error fetching artist {artist_id} meta data from MusicBrainz: {exc}") + + return meta + +def get_artist_relation_links(relationlist): + """Parse and extract named links from a MusicBrainz URL relation list.""" + links = {} + for rel in relationlist: + target = rel.get('target') + if not target: + continue + if re.match(r'^https://.*imdb\.com', target): + links['IMDb'] = target + elif re.match(r'^https://music\.apple\.com', target): + links['apple'] = target + elif re.match(r'^https://music\.amazon\.com', target): + links['amazon'] = target + elif re.match(r'^https://open\.spotify\.com', target): + links['spotify'] = target + elif re.match(r'^https://.*qobuz\.com', target): + links['qobuz'] = target + elif re.match(r'^https://.*deezer\.com', target): + links['deezer'] = target + elif re.match(r'^https://.*beatport\.com', target): + links['beatport'] = target + return links def get_artist_releases(artist_id): """Fetch releases for a given artist ID from MusicBrainz.""" @@ -83,7 +133,7 @@ def get_artist_releases(artist_id): result = musicbrainzngs.browse_releases( artist=artist_id, release_type=['album'], - includes=['release-groups'] + includes=['release-groups', 'url-rels'] ) releases = [] diff --git a/mzb_rss_service/templates/feed.html b/mbz_rss_service/templates/feed.html similarity index 91% rename from mzb_rss_service/templates/feed.html rename to mbz_rss_service/templates/feed.html index 3ec97f5..01b65a9 100644 --- a/mzb_rss_service/templates/feed.html +++ b/mbz_rss_service/templates/feed.html @@ -9,6 +9,11 @@ {% for artist in feed.artists %}
  • {{ artist.name }} + {% if artist.links %} + {% for type, link in artist.links.items() %} + {{ type }}{% if not loop.last %} | {% endif %} + {% endfor %} + {% endif %}
    diff --git a/mzb_rss_service/templates/feed.xml b/mbz_rss_service/templates/feed.xml similarity index 72% rename from mzb_rss_service/templates/feed.xml rename to mbz_rss_service/templates/feed.xml index 37a6816..fd0448e 100644 --- a/mzb_rss_service/templates/feed.xml +++ b/mbz_rss_service/templates/feed.xml @@ -14,10 +14,17 @@ https://musicbrainz.org/release/{{ release.id }} {{ release.id }} + {% endif %}

    {{ release.artist.name }} {{ release.title }}
    Release Date: {{ release.date }}

    - {% if release.hasCoverArt %} - Cover Art {{ release.title }} + {% if release.links %} +

    Links: + {% for type, url in release.links.items() %} + {{ type | capitalize }}{% if not loop.last %} | {% endif %} + {% endfor %} +

    {% endif %} ]]>
    {% if release.pub_date %}