From a03cda10eced3e00fd97c3dcb303ce400a294b1b Mon Sep 17 00:00:00 2001 From: Robert Honz Date: Mon, 16 Dec 2024 14:42:53 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Playlist=20can=20be=20created=20for?= =?UTF-8?q?=20downloaded=20albums,=20mixes,=20UserPlaylists=20etc.=20Fixes?= =?UTF-8?q?=20#108?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 ++ tidal_dl_ng/constants.py | 2 ++ tidal_dl_ng/download.py | 57 ++++++++++++++++++++++++++++++++++------ 3 files changed, 53 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index ebda126..f36c0eb 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,8 @@ If you like to have the GUI version only, have a look at the - Adjustable audio and video download quality. - FLAC extraction from MP4 containers - Lyrics and album art / cover download +- Creates playlist files +- Can symlink tracks instead of having several copies, if added to different playlist ## ▶️ Getting started with development diff --git a/tidal_dl_ng/constants.py b/tidal_dl_ng/constants.py index 10fb93d..7da2711 100644 --- a/tidal_dl_ng/constants.py +++ b/tidal_dl_ng/constants.py @@ -9,6 +9,8 @@ COVER_NAME: str = "cover.jpg" BLOCK_SIZE: int = 4096 BLOCKS: int = 1024 CHUNK_SIZE: int = BLOCK_SIZE * BLOCKS +PLAYLIST_EXTENSION: str = ".m3u" +PLAYLIST_PREFIX: str = "_" class QualityVideo(StrEnum): diff --git a/tidal_dl_ng/download.py b/tidal_dl_ng/download.py index 509b810..6679371 100644 --- a/tidal_dl_ng/download.py +++ b/tidal_dl_ng/download.py @@ -23,6 +23,8 @@ from tidal_dl_ng.constants import ( CHUNK_SIZE, COVER_NAME, EXTENSION_LYRICS, + PLAYLIST_EXTENSION, + PLAYLIST_PREFIX, REQUESTS_TIMEOUT_SEC, MediaType, QualityVideo, @@ -464,15 +466,16 @@ class Download: os.makedirs(path_media_dst.parent, exist_ok=True) # Move item and symlink it - if self.skip_existing: - skip_file: bool = check_file_exists(path_media_dst, extension_ignore=True) - else: - skip_file: bool = False + if path_media_dst != path_media_src: + if self.skip_existing: + skip_file: bool = check_file_exists(path_media_dst, extension_ignore=True) + else: + skip_file: bool = False - if not skip_file: - shutil.move(path_media_src, path_media_dst) + if not skip_file: + shutil.move(path_media_src, path_media_dst) - os.symlink(path_media_dst, path_media_src) + os.symlink(path_media_dst, path_media_src) return path_media_dst @@ -642,7 +645,7 @@ class Download: raise MediaMissing # Create file name and path - file_name_relative = format_path_media(file_template, media, self.settings.data.album_track_num_pad_min) + file_name_relative: str = format_path_media(file_template, media, self.settings.data.album_track_num_pad_min) # Get the name of the list and check, if videos should be included. list_media_name: str = name_builder_title(media) @@ -664,6 +667,7 @@ class Download: ) is_album: bool = isinstance(media, Album) + result_dirs: [pathlib.Path] = [] # Iterate through list items while not self.progress.finished: @@ -685,6 +689,7 @@ class Download: for future in futures.as_completed(l_futures): # Retrieve result status, result_path_file = future.result() + result_dirs.append(result_path_file.parent) # Advance progress bar. self.progress.advance(p_task1) @@ -692,8 +697,44 @@ class Download: if not progress_stdout: self.progress_gui.list_item.emit(self.progress.tasks[p_task1].percentage) + # Create playlist file + if self.settings.data.playlist_create: + self.playlist_populate(set(result_dirs), list_media_name, is_album) + self.fn_logger.info(f"Finished list '{list_media_name}'.") + def playlist_populate(self, dirs_scoped: [pathlib.Path], name_list: str, is_album: bool) -> [pathlib.Path]: + result: [pathlib.Path] = [] + + # For each dir, which contains tracks + for dir_scoped in dirs_scoped: + # Sanitize final playlist name to fit into OS boundaries. + path_playlist = dir_scoped / (PLAYLIST_PREFIX + name_list + PLAYLIST_EXTENSION) + path_playlist = pathlib.Path(path_file_sanitize(path_playlist, adapt=True)) + + # Get all tracks in the directory + path_tracks: [pathlib.Path] = [] + + for extension_audio in AudioExtensions: + path_tracks = path_tracks + list(dir_scoped.glob(f"*{extension_audio!s}")) + + # If it is not an album sort by modification time + if not is_album: + path_tracks.sort(key=lambda x: os.path.getmtime(x)) + + # Write data to m3u file + with path_playlist.open(mode="w", encoding="utf-8") as f: + for path_track in path_tracks: + # If its a symlink write the relative file path to the actual track into the playlist file + if path_track.is_symlink(): + media_file_target = path_track.resolve().relative_to(path_track.parent, walk_up=True) + + f.write(str(media_file_target) + os.linesep) + + result.append(path_playlist) + + return result + def _video_convert(self, path_file: pathlib.Path) -> pathlib.Path: path_file_out: pathlib.Path = path_file.with_suffix(AudioExtensions.MP4) result, _ = (