add related links

artist links to feed page (/feed/:ID)
release links to feed xml (release description)
This commit is contained in:
Volker
2025-12-27 17:11:17 +01:00
parent 6efa6617d3
commit 93eef59aa8
5 changed files with 82 additions and 11 deletions
+8 -2
View File
@@ -68,10 +68,13 @@ class Config:
backup_path = f"{file_path}.{datetime.now().strftime('%Y%m%d%H%M%S')}.bak" backup_path = f"{file_path}.{datetime.now().strftime('%Y%m%d%H%M%S')}.bak"
shutil.copy(file_path, backup_path) shutil.copy(file_path, backup_path)
logger.debug(f"Saving yaml to {file_path}")
os.makedirs(os.path.dirname(file_path), exist_ok=True) os.makedirs(os.path.dirname(file_path), exist_ok=True)
with open(file_path, 'w') as f: with open(file_path, 'w') as f:
yaml.dump(data, f, default_flow_style=False, sort_keys=False) yaml.dump(data, f, default_flow_style=False, sort_keys=False)
logger.debug(f"Saved yaml to {file_path}")
@property @property
def feeds(self): def feeds(self):
return self._feeds_data.get('feeds', []) 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._feeds_data['feeds'] = [feed for feed in self.feeds if feed['id'] != feed_id]
self.save_feeds() 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: for feed in self.feeds:
if feed['id'] == feed_id: if feed['id'] == feed_id:
if 'artists' not in feed: if 'artists' not in feed:
feed['artists'] = [] feed['artists'] = []
if not any(a['id'] == artist_id for a in 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() feed['updated_at'] = datetime.now(timezone.utc).isoformat()
self.save_feeds() self.save_feeds()
break break
+7 -4
View File
@@ -9,6 +9,8 @@ import os
import sys import sys
from . import musicbrainz from . import musicbrainz
XML_CONTENT_TYPE = "application/xml"
log_level_str = os.environ.get('LOG_LEVEL', 'INFO').upper() log_level_str = os.environ.get('LOG_LEVEL', 'INFO').upper()
log_level = getattr(logging, log_level_str, logging.INFO) 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')) 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: try:
with open(cache_file, 'r') as f: with open(cache_file, 'r') as f:
logger.debug(f"Serving cached feed for {feed_id}") 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: except IOError as e:
logger.warning(f"Could not read cache file {cache_file}: {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') artist_name = request.form.get('artist_name')
logger.debug(f"Request to add artist '{artist_name}' ({artist_id}) to feed {feed_id}") logger.debug(f"Request to add artist '{artist_name}' ({artist_id}) to feed {feed_id}")
if artist_id and artist_name: 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)) return redirect(url_for('edit_feed', feed_id=feed_id))
@app.route("/feed/<feed_id>/artist/<artist_id>/remove", methods=["POST"]) @app.route("/feed/<feed_id>/artist/<artist_id>/remove", methods=["POST"])
@@ -228,7 +231,7 @@ def opml():
logger.debug("Request for OPML file") logger.debug("Request for OPML file")
feeds = config.feeds feeds = config.feeds
opml_content = render_template('opml.xml', feeds=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/<feed_id>') @app.route('/feed/<feed_id>')
def get_feed_rss(feed_id): def get_feed_rss(feed_id):
@@ -277,7 +280,7 @@ def get_feed_rss(feed_id):
except IOError as e: except IOError as e:
logger.warning(f"Could not write feed '{feed_data['name']}' to cache file {cache_file}: {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") @app.route("/health")
def health_check(): def health_check():
@@ -1,6 +1,6 @@
import re
import musicbrainzngs import musicbrainzngs
import logging import logging
import os
from .config import config from .config import config
from datetime import datetime from datetime import datetime
from email.utils import formatdate from email.utils import formatdate
@@ -62,6 +62,10 @@ def _process_release(release, artist_id):
release_date = release.get('date', 'Unknown') release_date = release.get('date', 'Unknown')
_, pub_date = _parse_release_date(release_date, release['title']) _, 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 { return {
'artist': { 'artist': {
'name': config.get_artist_name(artist_id), 'name': config.get_artist_name(artist_id),
@@ -72,8 +76,54 @@ def _process_release(release, artist_id):
'title': release['title'], 'title': release['title'],
'date': release_date, 'date': release_date,
'pub_date': pub_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): def get_artist_releases(artist_id):
"""Fetch releases for a given artist ID from MusicBrainz.""" """Fetch releases for a given artist ID from MusicBrainz."""
@@ -83,7 +133,7 @@ def get_artist_releases(artist_id):
result = musicbrainzngs.browse_releases( result = musicbrainzngs.browse_releases(
artist=artist_id, artist=artist_id,
release_type=['album'], release_type=['album'],
includes=['release-groups'] includes=['release-groups', 'url-rels']
) )
releases = [] releases = []
@@ -9,6 +9,11 @@
{% for artist in feed.artists %} {% for artist in feed.artists %}
<li> <li>
{{ artist.name }} {{ artist.name }}
{% if artist.links %}
{% for type, link in artist.links.items() %}
<a href="{{ link }}" target="_blank">{{ type }}</a>{% if not loop.last %} | {% endif %}
{% endfor %}
{% endif %}
<div class="item-actions"> <div class="item-actions">
<form class="form-inline" action="{{ url_for('remove_artist', feed_id=feed.id, artist_id=artist.id) }}" method="post"> <form class="form-inline" action="{{ url_for('remove_artist', feed_id=feed.id, artist_id=artist.id) }}" method="post">
<button type="submit">Remove</button> <button type="submit">Remove</button>
@@ -14,10 +14,17 @@
<link>https://musicbrainz.org/release/{{ release.id }}</link> <link>https://musicbrainz.org/release/{{ release.id }}</link>
<guid isPermaLink="false">{{ release.id }}</guid> <guid isPermaLink="false">{{ release.id }}</guid>
<description><![CDATA[ <description><![CDATA[
{% if release.hasCoverArt %}
<img src="https://coverartarchive.org/release/{{ release.id }}/front" alt="Cover Art {{ release.title }}">
{% endif %}
<p><b>{{ release.artist.name }}</b> <i>{{ release.title }}</i><br> <p><b>{{ release.artist.name }}</b> <i>{{ release.title }}</i><br>
Release Date: {{ release.date }}</p> Release Date: {{ release.date }}</p>
{% if release.hasCoverArt %} {% if release.links %}
<img src="http://coverartarchive.org/release/{{ release.id }}/front-500" alt="Cover Art {{ release.title }}"> <p><b>Links: </b>
{% for type, url in release.links.items() %}
<a href="{{ url }}" target="_blank">{{ type | capitalize }}</a>{% if not loop.last %} | {% endif %}
{% endfor %}
</p>
{% endif %} {% endif %}
]]></description> ]]></description>
{% if release.pub_date %} {% if release.pub_date %}