From c3ea528f17c604b8a641de54460be10fa1daf36b Mon Sep 17 00:00:00 2001 From: Xoconoch Date: Sun, 3 Aug 2025 14:39:00 -0600 Subject: [PATCH] improve m3u format --- .gitignore | 1 + deezspot/deezloader/__download__.py | 2 +- deezspot/libutils/write_m3u.py | 107 +++++++++++++++++++++++++--- deezspot/spotloader/__download__.py | 4 +- 4 files changed, 100 insertions(+), 14 deletions(-) diff --git a/.gitignore b/.gitignore index 0b441bf..1813ee2 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ callback.log .gitignore deezspot_test.log spotify_downloads +deezer_spo_downloads diff --git a/deezspot/deezloader/__download__.py b/deezspot/deezloader/__download__.py index cd0ed5d..cc05d3c 100644 --- a/deezspot/deezloader/__download__.py +++ b/deezspot/deezloader/__download__.py @@ -1223,7 +1223,7 @@ class DW_PLAYLIST: if current_track_object: tracks.append(current_track_object) if current_track_object.success and hasattr(current_track_object, 'song_path') and current_track_object.song_path: - append_track_to_m3u(m3u_path, current_track_object.song_path) + append_track_to_m3u(m3u_path, current_track_object) if self.__make_zip: zip_name = f"{self.__output_dir}/{playlist_obj.title} [playlist {self.__ids}]" diff --git a/deezspot/libutils/write_m3u.py b/deezspot/libutils/write_m3u.py index 850d3ef..4f7d9a6 100644 --- a/deezspot/libutils/write_m3u.py +++ b/deezspot/libutils/write_m3u.py @@ -32,27 +32,105 @@ def create_m3u_file(output_dir: str, playlist_name: str) -> str: return m3u_path -def append_track_to_m3u(m3u_path: str, track_path: str) -> None: +def _get_track_duration_seconds(track: Track) -> int: """ - Appends a single track path to an existing m3u file. + Extract track duration in seconds from track metadata. + + Args: + track: Track object + + Returns: + int: Duration in seconds, defaults to 0 if not available + """ + try: + # Try to get duration from tags first + if hasattr(track, 'tags') and track.tags: + if 'duration' in track.tags: + return int(float(track.tags['duration'])) + elif 'length' in track.tags: + return int(float(track.tags['length'])) + + # Try to get from song_metadata if available + if hasattr(track, 'song_metadata') and hasattr(track.song_metadata, 'duration_ms'): + return int(track.song_metadata.duration_ms / 1000) + + # Fallback to 0 if no duration found + return 0 + except (ValueError, AttributeError, TypeError): + return 0 + + +def _get_track_info(track: Track) -> tuple: + """ + Extract artist and title information from track. + + Args: + track: Track object + + Returns: + tuple: (artist, title) strings + """ + try: + if hasattr(track, 'tags') and track.tags: + artist = track.tags.get('artist', 'Unknown Artist') + title = track.tags.get('music', track.tags.get('title', 'Unknown Title')) + return artist, title + elif hasattr(track, 'song_metadata'): + if hasattr(track.song_metadata, 'artists') and track.song_metadata.artists: + artist = ', '.join([a.name for a in track.song_metadata.artists]) + else: + artist = 'Unknown Artist' + title = getattr(track.song_metadata, 'title', 'Unknown Title') + return artist, title + else: + return 'Unknown Artist', 'Unknown Title' + except (AttributeError, TypeError): + return 'Unknown Artist', 'Unknown Title' + + +def append_track_to_m3u(m3u_path: str, track: Union[str, Track]) -> None: + """ + Appends a single track to an existing m3u file with extended format. Args: m3u_path: Full path to the m3u file - track_path: Full path to the track file + track: Track object or string path to track file """ - if not track_path or not os.path.exists(track_path): - return + if isinstance(track, str): + # Legacy support for string paths + track_path = track + if not track_path or not os.path.exists(track_path): + return - playlist_m3u_dir = os.path.dirname(m3u_path) - relative_path = os.path.relpath(track_path, start=playlist_m3u_dir) - - with open(m3u_path, "a", encoding="utf-8") as m3u_file: - m3u_file.write(f"{relative_path}\n") + playlist_m3u_dir = os.path.dirname(m3u_path) + relative_path = os.path.relpath(track_path, start=playlist_m3u_dir) + + with open(m3u_path, "a", encoding="utf-8") as m3u_file: + m3u_file.write(f"{relative_path}\n") + else: + # Track object with full metadata + if (not isinstance(track, Track) or + not track.success or + not hasattr(track, 'song_path') or + not track.song_path or + not os.path.exists(track.song_path)): + return + + playlist_m3u_dir = os.path.dirname(m3u_path) + relative_path = os.path.relpath(track.song_path, start=playlist_m3u_dir) + + # Get track metadata + duration = _get_track_duration_seconds(track) + artist, title = _get_track_info(track) + + with open(m3u_path, "a", encoding="utf-8") as m3u_file: + m3u_file.write(f"#EXTINF:{duration},{artist} - {title}\n") + m3u_file.write(f"{relative_path}\n") def write_tracks_to_m3u(output_dir: str, playlist_name: str, tracks: List[Track]) -> str: """ - Creates an m3u file and writes all successful tracks to it at once. + Creates an m3u file and writes all successful tracks to it at once using extended format. Args: output_dir: Base output directory @@ -79,6 +157,13 @@ def write_tracks_to_m3u(output_dir: str, playlist_name: str, tracks: List[Track] os.path.exists(track.song_path)): relative_song_path = os.path.relpath(track.song_path, start=playlist_m3u_dir) + + # Get track metadata + duration = _get_track_duration_seconds(track) + artist, title = _get_track_info(track) + + # Write EXTINF line with duration and metadata + m3u_file.write(f"#EXTINF:{duration},{artist} - {title}\n") m3u_file.write(f"{relative_song_path}\n") logger.info(f"Created m3u playlist file at: {m3u_path}") diff --git a/deezspot/spotloader/__download__.py b/deezspot/spotloader/__download__.py index deae485..4f4e956 100644 --- a/deezspot/spotloader/__download__.py +++ b/deezspot/spotloader/__download__.py @@ -1132,9 +1132,9 @@ class DW_PLAYLIST: if track: tracks.append(track) - # --- Append the final track path to the m3u file using a relative path --- + # --- Append the final track to the m3u file with extended format --- if track and track.success and hasattr(track, 'song_path') and track.song_path: - append_track_to_m3u(m3u_path, track.song_path) + append_track_to_m3u(m3u_path, track) # --------------------------------------------------------------------- if self.__make_zip: