From e4a948cd2a62c993b6d91e6691f062f313ee8a06 Mon Sep 17 00:00:00 2001 From: Xoconoch Date: Wed, 20 Aug 2025 09:27:36 -0500 Subject: [PATCH] feat: Add `quality` and `bitrate` params to summary object --- deezspot/deezloader/__download__.py | 67 +++++++++++++++++++++++++++ deezspot/models/callback/callbacks.py | 3 ++ deezspot/spotloader/__download__.py | 64 +++++++++++++++++++++++++ docs/callback_examples.md | 16 +++++-- 4 files changed, 146 insertions(+), 4 deletions(-) diff --git a/deezspot/deezloader/__download__.py b/deezspot/deezloader/__download__.py index a978e2e..79f5f69 100644 --- a/deezspot/deezloader/__download__.py +++ b/deezspot/deezloader/__download__.py @@ -552,6 +552,27 @@ class EASY_DW: ) summary.final_path = final_path_val summary.download_quality = download_quality_val + # Compute final quality/bitrate + quality_val = None + bitrate_val = None + if self.__convert_to: + fmt, brp = self._parse_format_string(self.__convert_to) + if fmt: + quality_val = fmt.lower() + if brp: + bitrate_val = brp.lower().replace('kbps', 'k') + else: + qkey = (self.__quality_download or '').upper() + if qkey.startswith('MP3'): + quality_val = 'mp3' + try: + bitrate_val = f"{qkey.split('_')[1]}k" + except Exception: + bitrate_val = None + elif qkey == 'FLAC': + quality_val = 'flac' + summary.quality = quality_val + summary.bitrate = bitrate_val done_status.summary = summary callback_obj = trackCallbackObject( @@ -1329,6 +1350,29 @@ class DW_ALBUM: total_failed=len(failed_tracks_cb), service=Service.DEEZER ) + # Compute and attach final media characteristics + quality_val = None + bitrate_val = None + if getattr(self.__preferences, 'convert_to', None): + fmt = getattr(self.__preferences, 'convert_to', None) + if fmt: + quality_val = fmt.lower() + br_raw = getattr(self.__preferences, 'bitrate', None) + if br_raw: + digits = ''.join([c for c in str(br_raw) if c.isdigit()]) + bitrate_val = f"{digits}k" if digits else None + else: + qkey = (self.__quality_download or '').upper() + if qkey.startswith('MP3'): + quality_val = 'mp3' + try: + bitrate_val = f"{qkey.split('_')[1]}k" + except Exception: + bitrate_val = None + elif qkey == 'FLAC': + quality_val = 'flac' + summary_obj.quality = quality_val + summary_obj.bitrate = bitrate_val # Report album completion status report_album_done(album_obj, summary_obj) @@ -1452,6 +1496,29 @@ class DW_PLAYLIST: total_failed=len(failed_tracks_cb), service=Service.DEEZER ) + # Compute and attach final media characteristics + quality_val = None + bitrate_val = None + if getattr(self.__preferences, 'convert_to', None): + fmt = getattr(self.__preferences, 'convert_to', None) + if fmt: + quality_val = fmt.lower() + br_raw = getattr(self.__preferences, 'bitrate', None) + if br_raw: + digits = ''.join([c for c in str(br_raw) if c.isdigit()]) + bitrate_val = f"{digits}k" if digits else None + else: + qkey = (self.__quality_download or '').upper() + if qkey.startswith('MP3'): + quality_val = 'mp3' + try: + bitrate_val = f"{qkey.split('_')[1]}k" + except Exception: + bitrate_val = None + elif qkey == 'FLAC': + quality_val = 'flac' + summary_obj.quality = quality_val + summary_obj.bitrate = bitrate_val # Attach m3u path to summary summary_obj.m3u_path = m3u_path diff --git a/deezspot/models/callback/callbacks.py b/deezspot/models/callback/callbacks.py index d67e8a3..0e9526a 100644 --- a/deezspot/models/callback/callbacks.py +++ b/deezspot/models/callback/callbacks.py @@ -75,6 +75,9 @@ class summaryObject: m3u_path: Optional[str] = None final_path: Optional[str] = None download_quality: Optional[str] = None + # Final media characteristics + quality: Optional[str] = None # e.g., "mp3", "flac", "ogg" + bitrate: Optional[str] = None # e.g., "320k" @dataclass diff --git a/deezspot/spotloader/__download__.py b/deezspot/spotloader/__download__.py index 4c800a9..1e2d8d5 100644 --- a/deezspot/spotloader/__download__.py +++ b/deezspot/spotloader/__download__.py @@ -718,6 +718,28 @@ class EASY_DW: } summary_obj.final_path = final_path_val summary_obj.download_quality = sp_quality_map_single.get(quality_key_single, 'OGG') + # Compute final quality/bitrate + quality_val = None + bitrate_val = None + if self.__convert_to: + # When converting, trust convert_to + bitrate + fmt = self.__convert_to + if fmt: + quality_val = fmt.lower() + br_raw = self.__bitrate + if br_raw: + digits = ''.join([c for c in str(br_raw) if c.isdigit()]) + bitrate_val = f"{digits}k" if digits else None + else: + quality_val = 'ogg' + if quality_key_single == 'NORMAL': + bitrate_val = '96k' + elif quality_key_single == 'HIGH': + bitrate_val = '160k' + elif quality_key_single == 'VERY_HIGH': + bitrate_val = '320k' + summary_obj.quality = quality_val + summary_obj.bitrate = bitrate_val # Report track done status # Compute final path and quality label @@ -1123,6 +1145,27 @@ class DW_ALBUM: total_failed=len(failed_tracks), service=Service.SPOTIFY ) + # Compute final quality/bitrate for album summary + quality_val = None + bitrate_val = None + conv = getattr(self.__preferences, 'convert_to', None) + if conv: + quality_val = conv.lower() + br_raw = getattr(self.__preferences, 'bitrate', None) + if br_raw: + digits = ''.join([c for c in str(br_raw) if c.isdigit()]) + bitrate_val = f"{digits}k" if digits else None + else: + quality_val = 'ogg' + qkey = (getattr(self.__preferences, 'quality_download', None) or 'NORMAL').upper() + if qkey == 'NORMAL': + bitrate_val = '96k' + elif qkey == 'HIGH': + bitrate_val = '160k' + elif qkey == 'VERY_HIGH': + bitrate_val = '320k' + summary_obj.quality = quality_val + summary_obj.bitrate = bitrate_val report_album_done(album_obj, summary_obj) @@ -1256,6 +1299,27 @@ class DW_PLAYLIST: total_failed=len(failed_tracks_cb), service=Service.SPOTIFY ) + # Compute final quality/bitrate for playlist summary + quality_val = None + bitrate_val = None + conv = getattr(self.__preferences, 'convert_to', None) + if conv: + quality_val = conv.lower() + br_raw = getattr(self.__preferences, 'bitrate', None) + if br_raw: + digits = ''.join([c for c in str(br_raw) if c.isdigit()]) + bitrate_val = f"{digits}k" if digits else None + else: + quality_val = 'ogg' + qkey = (getattr(self.__preferences, 'quality_download', None) or 'NORMAL').upper() + if qkey == 'NORMAL': + bitrate_val = '96k' + elif qkey == 'HIGH': + bitrate_val = '160k' + elif qkey == 'VERY_HIGH': + bitrate_val = '320k' + summary_obj.quality = quality_val + summary_obj.bitrate = bitrate_val # Include m3u path in summary and callback report_playlist_done(playlist_obj_for_cb, summary_obj, m3u_path=m3u_path) diff --git a/docs/callback_examples.md b/docs/callback_examples.md index 0a4cf9b..9fc905f 100644 --- a/docs/callback_examples.md +++ b/docs/callback_examples.md @@ -2,7 +2,9 @@ This document provides examples for all possible callback JSON objects as generated by `deezspot`. These examples are based on the dataclasses defined in `deezspot/models/callback/`. -These callbacks are service-agnostic, which means they will have the same exact format regardless of the service used. Basically: one interface to rule them both. +These callbacks are service-agnostic, which means they will have the same exact format regardless of the service used. Basically: one interface to rule them both. + +When a `summary` is included, it specifies the source service via a `service` field (either `spotify` or `deezer`). It also includes final media characteristics: `quality` (final format, lowercase like "mp3", "flac", "ogg") and `bitrate` (e.g., "320k"). However, don't be so confident this will perfectly unify both service's APIs, since there are still key differences in the metadata itself, for example: in spotify, certain band shows up as [Man**รก**](https://open.spotify.com/artist/7okwEbXzyT2VffBmyQBWLz) but in deezer it shows up as [Man**a**](https://www.deezer.com/us/artist/4115). There's really nothing to be done in those cases other than deal with it, since the inconsistency is not a matter of _how_ the services store their data but literally _what_ data they store. @@ -576,7 +578,7 @@ Indicates that the track has been processed successfully. } ``` -Note: When a single-track operation is performed (not part of an album/playlist), the `summary` field may be populated. In that case, it can also include `final_path` and `download_quality` as additional convenience fields mirroring `status_info`. +Note: When a single-track operation is performed (not part of an album/playlist), the `summary` field may be populated. In that case, it can also include `final_path`, `download_quality`, `service`, `quality`, and `bitrate` as additional convenience fields indicating the final output. ## `albumCallbackObject` Examples @@ -722,7 +724,10 @@ The `done` status for an album includes a summary of all track operations. "failed_tracks": [], "total_successful": 1, "total_skipped": 0, - "total_failed": 0 + "total_failed": 0, + "service": "spotify", + "quality": "ogg", + "bitrate": "320k" }, "ids": { "spotify": "6l8GvAOfAccdwJAIF13j3m" @@ -887,7 +892,10 @@ The `done` status for a playlist includes a summary of all track operations. "total_successful": 1, "total_skipped": 0, "total_failed": 1, - "m3u_path": "/playlists/Classic Rock Anthems.m3u" + "m3u_path": "/playlists/Classic Rock Anthems.m3u", + "service": "spotify", + "quality": "ogg", + "bitrate": "320k" }, "ids": { "spotify": "37i9dQZF1DX1rVvRgjX59F"