Compare commits

...

6 Commits
27 ... 29

5 changed files with 59 additions and 56 deletions

View File

@ -479,6 +479,10 @@ https://life.lab.unbl.ink/scrobble/e39779c8-62a5-46a6-bdef-fb7662810dc6/start/
- Note taken on [2025-09-30 Tue 09:33]
This may have already been resolved ... need to just confirm it.
* Version 29.0 [1/1]
** DONE HOTFIX podcast lookups, final
* Version 28.0 [1/1]
** DONE HOTFIX podcast lookups
* Version 27.0 [3/3]
** DONE [#A] Fix bug where podcast scrobbling creates duplicate Podcast :project:vrobbler:scrobbling:podcasts:bug:personal:
:PROPERTIES:

View File

@ -115,6 +115,12 @@ def mopidy_podcast_request_data():
mopidy_uri=mopidy_uri, artist="NPR", album="Up First"
).request_json
@pytest.fixture
def mopidy_podcast_https_request_data():
mopidy_uri = "podcast+https://feeds.npr.org/510318/podcast.xml#85b9c4c4-ae09-43d9-8853-31ccf43f68e6"
return MopidyRequest(
mopidy_uri=mopidy_uri, artist="NPR", album="Up First"
).request_json
class JellyfinTrackRequest:
name = "Emotion"

View File

@ -143,13 +143,13 @@ class PodcastEpisode(ScrobblableMixin):
def find_or_create(
cls,
title: str,
podcast_name: str,
podcast_description: str,
pub_date: str,
number: int = 0,
mopidy_uri: str = "",
producer_name: str = "",
episode_num: int = 0,
run_time_seconds: int = 1800,
mopidy_uri: str = "",
podcast_name: str = "",
podcast_producer: str = "",
podcast_description: str = "",
enrich: bool = True,
) -> "PodcastEpisode":
"""Given a data dict from Mopidy, finds or creates a podcast and
@ -158,8 +158,8 @@ class PodcastEpisode(ScrobblableMixin):
"""
log_context={"mopidy_uri": mopidy_uri, "media_type": "Podcast"}
producer = None
if producer_name:
producer = Producer.find_or_create(producer_name)
if podcast_producer:
producer = Producer.find_or_create(podcast_producer)
podcast, created = Podcast.objects.get_or_create(name=podcast_name, defaults={"description": podcast_description})
log_context["podcast_id"] = podcast.id
@ -175,7 +175,7 @@ class PodcastEpisode(ScrobblableMixin):
podcast=podcast,
defaults={
"run_time_seconds": run_time_seconds,
"number": number,
"number": episode_num,
"pub_date": pub_date,
"mopidy_uri": mopidy_uri,
}

View File

@ -4,6 +4,7 @@ from typing import Any
from urllib.parse import unquote
import feedparser
import requests
from dateutil.parser import ParserError, parse
from podcasts.models import PodcastEpisode
@ -27,33 +28,43 @@ def fetch_metadata_from_rss(uri: str) -> dict[str, Any]:
log_context = {"mopidy_uri": uri, "media_type": "Podcast"}
podcast_data: dict[str, Any] = {}
rss_url = uri.split("#")[0].split("podcast+")[1]
target_guid = uri.split("#")[1]
log_context["rss_url"] = rss_url
log_context["target_guid"] = target_guid
try:
feed = feedparser.parse(uri.split("#")[0])
target_guid = uri.split("#")[1]
resp = requests.get(rss_url, timeout=10)
feed = feedparser.parse(resp.text)
except IndexError:
logger.warning("Tried to parse uri as RSS feed, but no target found", extra=log_context)
return podcast_data
podcast_publisher = feed.feed.get("itunes_publisher")
podcast_owner = feed.feed.itunes_owner.get("name") if isinstance(feed.feed.itunes_owner, dict) else feed.feed.itunes_owner
podcast_other = feed.feed.get("managingeditor") or feed.feed.get("copyright")
podcast_publisher = getattr(feed.feed, "itunes_publisher", "")
try:
podcast_owner = feed.feed.itunes_owner.get("name") if isinstance(feed.feed.itunes_owner, dict) else feed.feed.itunes_owner
podcast_other = feed.feed.get("managingeditor") or feed.feed.get("copyright")
except AttributeError:
podcast_owner = None
podcast_other = None
podcast_data = {
"podcast_name": feed.feed.get("title", "Unknown Podcast"),
"podcast_description": feed.feed.get("description", ""),
"podcast_link": feed.feed.get("link", ""),
"podcast_name": getattr(feed.feed, "title", ""),
# "podcast_description": getattr(feed.feed, "description", ""),
# "podcast_link": getattr(feed.feed, "link", ""),
"podcast_producer": podcast_publisher or podcast_owner or podcast_other
}
for entry in feed.entries:
if target_guid in target_guid:
if entry.get("guid") == target_guid:
logger.info("🎧 Episode found in RSS feed", extra=log_context)
podcast_data["episode_name"] = entry.title
podcast_data["episode_num"] = entry.guid
podcast_data["episode_pub_date"] = entry.get("published", None)
podcast_data["episode_description"] = entry.get("description", None)
podcast_data["episode_url"] = entry.enclosures[0].href if entry.get("enclosures") else None
podcast_data["episode_runtime_seconds"] = parse_duration(entry.get("itunes_duration", None))
podcast_data["title"] = entry.title
podcast_data["episode_num"] = int(entry.get("itunes_episode", 0))
podcast_data["pub_date"] = parse(entry.get("published", None))
podcast_data["run_time_seconds"] = parse_duration(entry.get("itunes_duration", None))
# podcast_data["description"] = entry.get("description", None)
# podcast_data["episode_url"] = entry.enclosures[0].href if entry.get("enclosures") else None
return podcast_data
else:
logger.info("Episode not found in RSS feed.")
@ -63,14 +74,14 @@ def parse_mopidy_uri(uri: str) -> dict[str, Any]:
podcast_data: dict[str, Any] = {}
logger.debug(f"Parsing URI: {uri}")
if "https://" in uri:
if "podcast+https" in uri:
return fetch_metadata_from_rss(uri)
parsed_uri = os.path.splitext(unquote(uri))[0].split("/")
podcast_data = {
"episode_filename": parsed_uri[-1],
"title": parsed_uri[-1],
"episode_num": None,
"podcast_name": parsed_uri[-2].strip(),
"pub_date": None,
@ -105,30 +116,14 @@ def parse_mopidy_uri(uri: str) -> dict[str, Any]:
if podcast_data["episode_num"]:
gap_to_strip += episode_num_pad
podcast_data["episode_name"] = episode_str[gap_to_strip:].replace("-", " ").strip()
podcast_data["title"] = episode_str[gap_to_strip:].replace("-", " ").strip()
return podcast_data
def get_or_create_podcast(post_data: dict) -> PodcastEpisode:
logger.info("Looking up podcast", extra={"post_data": post_data, "media_type": "Podcast"})
mopidy_uri = post_data.get("mopidy_uri", "")
parsed_data = parse_mopidy_uri(mopidy_uri)
producer_name = parsed_data.get("podcast_producer", post_data.get("artist", ""))
podcast_name = parsed_data.get("podcast_name", post_data.get("album", ""))
episode_name = parsed_data.get("episode_title", parsed_data.get("episode_filename", ""))
run_time_seconds = parsed_data.get("episode_runtime_seconds", post_data.get("run_time", 2700))
episode_dict = {
"title": episode_name,
"podcast_name": podcast_name,
"podcast_description": parsed_data.get("podcast_description"),
"pub_date": parsed_data.get("pub_date"),
"number": parsed_data.get("episode_num"),
"mopidy_uri": mopidy_uri,
"producer_name": producer_name,
"run_time_seconds": run_time_seconds,
}
return PodcastEpisode.find_or_create(**episode_dict)
return PodcastEpisode.find_or_create(**parsed_data)

View File

@ -56,18 +56,11 @@ def mopidy_scrobble_media(post_data: dict, user_id: int) -> Scrobble:
if media_type == Scrobble.MediaType.PODCAST_EPISODE:
parsed_data = parse_mopidy_uri(post_data.get("mopidy_uri", ""))
podcast_name = post_data.get(
"album", parsed_data.get("podcast_name", "")
)
if not parsed_data:
logger.warning("Tried to scrobble podcast but no uri found", extra={"post_data": post_data})
return Scrobble()
media_obj = PodcastEpisode.find_or_create(
title=parsed_data.get("episode_filename", ""),
podcast_name=podcast_name,
producer_name=post_data.get("artist", ""),
number=parsed_data.get("episode_num", ""),
pub_date=parsed_data.get("pub_date", ""),
mopidy_uri=post_data.get("mopidy_uri", ""),
)
media_obj = PodcastEpisode.find_or_create(**parsed_data)
else:
media_obj = Track.find_or_create(
title=post_data.get("name", ""),
@ -875,8 +868,13 @@ def manual_scrobble_webpage(
)
scrobble = Scrobble.create_or_update(webpage, user_id, scrobble_dict)
# possibly async this?
scrobble.push_to_archivebox()
if action == "stop":
scrobble.stop(force_finish=True)
else:
# possibly async this?
scrobble.push_to_archivebox()
return scrobble