Skip to content

Commit

Permalink
Merge pull request #1361 from spotDL/dev
Browse files Browse the repository at this point in the history
* bugfix: fixed m3u issues (#1357)


* bugfix: remove duplicate songs from songs_list (#1356)

* bugfix: fixed ytdl error reporting (#1360)

* Remembered to bump version number to 3.7.2
Are you proud of me? I actually did it before merging :)

Co-authored-by: Jakub Kot <42355410+xnetcat@users.noreply.github.com>
  • Loading branch information
Silverarmor and xnetcat authored Jul 30, 2021
2 parents 7bbe347 + e97c94a commit a8eef6e
Show file tree
Hide file tree
Showing 10 changed files with 177 additions and 102 deletions.
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[metadata]
version = 3.7.1
version = 3.7.2

name = spotdl
url = https://github.com/spotDL/spotify-downloader
Expand Down
75 changes: 1 addition & 74 deletions spotdl/download/downloader.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from spotdl.search import SongObject
from spotdl.download.progress_ui_handler import YTDLLogger
from spotdl.download import ffmpeg, set_id3_data, DisplayManager, DownloadTracker
from spotdl.providers.provider_utils import _get_converted_file_path


class DownloadManager:
Expand Down Expand Up @@ -176,7 +177,6 @@ async def download_song(self, song_object: SongObject) -> None:
"outtmpl": f"{str(temp_folder)}/%(id)s.%(ext)s",
"quiet": True,
"no_warnings": True,
"ignoreerrors": True,
"logger": YTDLLogger(),
"progress_hooks": [display_progress_tracker.ytdl_progress_hook]
if display_progress_tracker
Expand Down Expand Up @@ -276,81 +276,8 @@ def _perform_audio_download(
except Exception as e: # noqa:E722
# ! This is equivalent to a failed download, we do nothing, the song remains on
# ! download_trackers download queue and all is well...

temp_files = Path(temp_folder).glob(f"{converted_file_name}.*")
for temp_file in temp_files:
temp_file.unlink()

raise e


# ========================
# === Helper function ===
# ========================


def _sanitize_filename(input_str: str) -> str:
output = input_str

# ! this is windows specific (disallowed chars)
output = "".join(char for char in output if char not in "/?\\*|<>")

# ! double quotes (") and semi-colons (:) are also disallowed characters but we would
# ! like to retain their equivalents, so they aren't removed in the prior loop
output = output.replace('"', "'").replace(":", "-")

return output


def _get_smaller_file_path(input_song: SongObject, output_format: str) -> Path:
# Only use the first artist if the song path turns out to be too long
smaller_name = f"{input_song.contributing_artists[0]} - {input_song.song_name}"

smaller_name = _sanitize_filename(smaller_name)

try:
return Path(f"{smaller_name}.{output_format}").resolve()
except (OSError, WindowsError):
# Expected to happen in the rare case when the saved path is too long,
# even with the short filename
raise OSError("Cannot save song due to path issues.")


def _get_converted_file_path(song_obj: SongObject, output_format: str = None) -> Path:

# ! we eliminate contributing artist names that are also in the song name, else we
# ! would end up with things like 'Jetta, Mastubs - I'd love to change the world
# ! (Mastubs REMIX).mp3' which is kinda an odd file name.

# also make sure that main artist is included in artistStr even if they
# are in the song name, for example
# Lil Baby - Never Recover (Lil Baby & Gunna, Drake).mp3

artists_filtered = []

if output_format is None:
output_format = "mp3"

for artist in song_obj.contributing_artists:
if artist.lower() not in song_obj.song_name:
artists_filtered.append(artist)
elif artist.lower() is song_obj.contributing_artists[0].lower():
artists_filtered.append(artist)

artist_str = ", ".join(artists_filtered)

converted_file_name = _sanitize_filename(
f"{artist_str} - {song_obj.song_name}.{output_format}"
)

converted_file_path = Path(converted_file_name)

# ! Checks if a file name is too long (256 max on both linux and windows)
try:
if len(str(converted_file_path.resolve().name)) > 256:
print("Path was too long. Using Small Path.")
return _get_smaller_file_path(song_obj, output_format)
except (OSError, WindowsError):
return _get_smaller_file_path(song_obj, output_format)

return converted_file_path
2 changes: 1 addition & 1 deletion spotdl/download/ffmpeg.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ async def convert(

proc_out = await process.communicate()

if proc_out[0] and proc_out[1]:
if proc_out[0] or proc_out[1]:
out = str(b"".join(proc_out))
else:
out = ""
Expand Down
2 changes: 1 addition & 1 deletion spotdl/download/progress_ui_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def warning(self, msg):
pass

def error(self, msg):
pass
raise Exception(msg)


class SizedTextColumn(ProgressColumn):
Expand Down
12 changes: 10 additions & 2 deletions spotdl/parsers/query_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,15 @@ def parse_query(
# linefeed to visually separate output for each query
print()

return songs_list
# remove duplicates
seen_songs = set()
songs = []
for song in songs_list:
if song.file_name not in seen_songs:
songs.append(song)
seen_songs.add(song.file_name)

return songs


def parse_request(
Expand Down Expand Up @@ -64,7 +72,7 @@ def parse_request(
elif "open.spotify.com" in request and "album" in request:
print("Fetching Album...")
song_list = song_gatherer.from_album(
request, output_format, use_youtube, threads
request, output_format, use_youtube, generate_m3u, threads
)
elif "open.spotify.com" in request and "playlist" in request:
print("Fetching Playlist...")
Expand Down
70 changes: 69 additions & 1 deletion spotdl/providers/provider_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from typing import List
from rapidfuzz import fuzz
from bs4 import BeautifulSoup
from pathlib import Path


def _match_percentage(str1: str, str2: str, score_cutoff: float = 0) -> float:
Expand Down Expand Up @@ -60,7 +61,7 @@ def _parse_duration(duration: str) -> float:

def _create_song_title(song_name: str, song_artists: List[str]) -> str:
joined_artists = ", ".join(song_artists)
return f"{joined_artists} - {song_name}".lower()
return f"{joined_artists} - {song_name}"


def _get_song_lyrics(song_name: str, song_artists: List[str]) -> str:
Expand Down Expand Up @@ -102,3 +103,70 @@ def _get_song_lyrics(song_name: str, song_artists: List[str]) -> str:
return ""
except: # noqa: E722
return ""


def _sanitize_filename(input_str: str) -> str:
output = input_str

# ! this is windows specific (disallowed chars)
output = "".join(char for char in output if char not in "/?\\*|<>")

# ! double quotes (") and semi-colons (:) are also disallowed characters but we would
# ! like to retain their equivalents, so they aren't removed in the prior loop
output = output.replace('"', "'").replace(":", "-")

return output


def _get_smaller_file_path(input_song, output_format: str) -> Path:
# Only use the first artist if the song path turns out to be too long
smaller_name = f"{input_song.contributing_artists[0]} - {input_song.song_name}"

smaller_name = _sanitize_filename(smaller_name)

try:
return Path(f"{smaller_name}.{output_format}").resolve()
except (OSError, WindowsError):
# Expected to happen in the rare case when the saved path is too long,
# even with the short filename
raise OSError("Cannot save song due to path issues.")


def _get_converted_file_path(song_obj, output_format: str = None) -> Path:

# ! we eliminate contributing artist names that are also in the song name, else we
# ! would end up with things like 'Jetta, Mastubs - I'd love to change the world
# ! (Mastubs REMIX).mp3' which is kinda an odd file name.

# also make sure that main artist is included in artistStr even if they
# are in the song name, for example
# Lil Baby - Never Recover (Lil Baby & Gunna, Drake).mp3

artists_filtered = []

if output_format is None:
output_format = "mp3"

for artist in song_obj.contributing_artists:
if artist.lower() not in song_obj.song_name:
artists_filtered.append(artist)
elif artist.lower() is song_obj.contributing_artists[0].lower():
artists_filtered.append(artist)

artist_str = ", ".join(artists_filtered)

converted_file_name = _sanitize_filename(
f"{artist_str} - {song_obj.song_name}.{output_format}"
)

converted_file_path = Path(converted_file_name)

# ! Checks if a file name is too long (256 max on both linux and windows)
try:
if len(str(converted_file_path.resolve().name)) > 256:
print("Path was too long. Using Small Path.")
return _get_smaller_file_path(song_obj, output_format)
except (OSError, WindowsError):
return _get_smaller_file_path(song_obj, output_format)

return converted_file_path
4 changes: 2 additions & 2 deletions spotdl/providers/yt_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ def search_and_get_best_match(
if isrc_result is not None and isrc_result.watch_url is not None:
return isrc_result.watch_url

song_title = _create_song_title(song_name, song_artists)
song_title = _create_song_title(song_name, song_artists).lower()

# Query YTM by songs only first, this way if we get correct result on the first try
# we don't have to make another request to ytmusic api that could result in us
Expand Down Expand Up @@ -126,7 +126,7 @@ def _order_yt_results(
continue

artist_match = (artist_match_number / len(song_artists)) * 100
song_title = _create_song_title(song_name, song_artists)
song_title = _create_song_title(song_name, song_artists).lower()
name_match = round(
_match_percentage(
unidecode(result.title.lower()), unidecode(song_title), 60
Expand Down
6 changes: 3 additions & 3 deletions spotdl/providers/ytm_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ def search_and_get_best_match(
):
return isrc_result["link"]

song_title = _create_song_title(song_name, song_artists)
song_title = _create_song_title(song_name, song_artists).lower()

# Query YTM by songs only first, this way if we get correct result on the first try
# we don't have to make another request to ytmusic api that could result in us
Expand All @@ -85,7 +85,7 @@ def search_and_get_best_match(
# We didn't find the correct song on the first try so now we get video type results
# add them to song_results, and get the result with highest score
video_results = _query_and_simplify(
_create_song_title(song_name, song_artists), filter="videos"
_create_song_title(song_name, song_artists).lower(), filter="videos"
)

# Order video results
Expand Down Expand Up @@ -194,7 +194,7 @@ def _order_ytm_results(

artist_match = (artist_match_number / len(song_artists)) * 100

song_title = _create_song_title(song_name, song_artists)
song_title = _create_song_title(song_name, song_artists).lower()

# Find name match and drop results below 60%
# this needs more testing
Expand Down
Loading

0 comments on commit a8eef6e

Please sign in to comment.