From: Marcel van der Veldt Date: Sun, 22 Feb 2026 20:18:34 +0000 (+0100) Subject: Fix fully_played should return boolean X-Git-Url: https://git.kitaultman.com/?a=commitdiff_plain;h=97dac07e11fd3455921da1dbe07f4e833fda0a8e;p=music-assistant-server.git Fix fully_played should return boolean --- diff --git a/music_assistant/controllers/media/audiobooks.py b/music_assistant/controllers/media/audiobooks.py index ad2cc476..65b463f2 100644 --- a/music_assistant/controllers/media/audiobooks.py +++ b/music_assistant/controllers/media/audiobooks.py @@ -18,6 +18,7 @@ from music_assistant.helpers.compare import ( from music_assistant.helpers.database import UNSET from music_assistant.helpers.datetime import utc_timestamp from music_assistant.helpers.json import serialize_to_json +from music_assistant.helpers.util import parse_optional_bool from music_assistant.models.music_provider import MusicProvider if TYPE_CHECKING: @@ -316,7 +317,7 @@ class AudiobooksController(MediaControllerBase[Audiobook]): # abort if nothing changed if ( cur_entry - and cur_entry["fully_played"] == media_item.fully_played + and parse_optional_bool(cur_entry["fully_played"]) == media_item.fully_played and abs((cur_entry["seconds_played"] or 0) - seconds_played) <= 2 ): return diff --git a/music_assistant/controllers/media/base.py b/music_assistant/controllers/media/base.py index f07b3cb9..2954a45a 100644 --- a/music_assistant/controllers/media/base.py +++ b/music_assistant/controllers/media/base.py @@ -34,7 +34,7 @@ from music_assistant.controllers.webserver.helpers.auth_middleware import get_cu from music_assistant.helpers.compare import compare_media_item, create_safe_string from music_assistant.helpers.database import UNSET from music_assistant.helpers.json import json_loads, serialize_to_json -from music_assistant.helpers.util import guard_single_request +from music_assistant.helpers.util import guard_single_request, parse_optional_bool if TYPE_CHECKING: from collections.abc import AsyncGenerator, Mapping @@ -1095,6 +1095,10 @@ class MediaControllerBase[ItemCls: "MediaItemType"](metaclass=ABCMeta): continue db_row_dict[key] = json_loads(raw_value) + # parse "fully_played" as bool if present in the row + if "fully_played" in db_row_dict: + db_row_dict["fully_played"] = parse_optional_bool(db_row_dict["fully_played"]) + # copy track_album --> album if track_album := db_row_dict.get("track_album"): db_row_dict["album"] = track_album diff --git a/music_assistant/controllers/media/podcasts.py b/music_assistant/controllers/media/podcasts.py index 1d777817..4c89c693 100644 --- a/music_assistant/controllers/media/podcasts.py +++ b/music_assistant/controllers/media/podcasts.py @@ -238,7 +238,7 @@ class PodcastsController(MediaControllerBase[Podcast]): if resume_info_db_row["seconds_played"]: episode.resume_position_ms = int(resume_info_db_row["seconds_played"] * 1000) if resume_info_db_row["fully_played"] is not None: - episode.fully_played = resume_info_db_row["fully_played"] + episode.fully_played = bool(resume_info_db_row["fully_played"]) # grab the episodes from the provider # note that we do not cache any of this because its diff --git a/music_assistant/controllers/music.py b/music_assistant/controllers/music.py index 16fc5180..03e833a9 100644 --- a/music_assistant/controllers/music.py +++ b/music_assistant/controllers/music.py @@ -75,7 +75,7 @@ from music_assistant.helpers.datetime import utc_timestamp from music_assistant.helpers.json import json_dumps, json_loads, serialize_to_json from music_assistant.helpers.tags import split_artists from music_assistant.helpers.uri import parse_uri -from music_assistant.helpers.util import TaskManager, parse_title_and_version +from music_assistant.helpers.util import TaskManager, parse_optional_bool, parse_title_and_version from music_assistant.models.core_controller import CoreController from music_assistant.models.music_provider import MusicProvider from music_assistant.models.smart_fades import SmartFadesAnalysis, SmartFadesAnalysisFragment @@ -1446,7 +1446,7 @@ class MusicController(CoreController): params["userid"] = userid if db_entry := await self.database.get_row(DB_TABLE_PLAYLOG, params): ma_position_ms = db_entry["seconds_played"] * 1000 if db_entry["seconds_played"] else 0 - ma_fully_played = db_entry["fully_played"] + ma_fully_played = parse_optional_bool(db_entry["fully_played"]) # Return the higher position to ensure users never lose progress if ma_position_ms >= provider_position_ms: diff --git a/music_assistant/helpers/util.py b/music_assistant/helpers/util.py index 2b8f5424..3e849f68 100644 --- a/music_assistant/helpers/util.py +++ b/music_assistant/helpers/util.py @@ -672,6 +672,23 @@ async def detect_charset(data: bytes, fallback: str = "utf-8") -> str: return fallback +def parse_optional_bool(value: Any) -> bool | None: + """Parse an optional boolean value from various input types.""" + if value is None: + return None + if isinstance(value, bool): + return value + if isinstance(value, str): + value_lower = value.strip().lower() + if value_lower in ("true", "1", "yes", "on"): + return True + if value_lower in ("false", "0", "no", "off"): + return False + if isinstance(value, (int, float)): + return bool(value) + return None + + def merge_dict( base_dict: dict[Any, Any], new_dict: dict[Any, Any],