Skip to content

Commit a8eef6e

Browse files
Silverarmorxnetcat
andauthored
Merge pull request spotDL#1361 from spotDL/dev
* bugfix: fixed m3u issues (spotDL#1357) * bugfix: remove duplicate songs from songs_list (spotDL#1356) * bugfix: fixed ytdl error reporting (spotDL#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 <[email protected]>
2 parents 7bbe347 + e97c94a commit a8eef6e

File tree

10 files changed

+177
-102
lines changed

10 files changed

+177
-102
lines changed

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[metadata]
2-
version = 3.7.1
2+
version = 3.7.2
33

44
name = spotdl
55
url = https://github.com/spotDL/spotify-downloader

spotdl/download/downloader.py

Lines changed: 1 addition & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from spotdl.search import SongObject
1212
from spotdl.download.progress_ui_handler import YTDLLogger
1313
from spotdl.download import ffmpeg, set_id3_data, DisplayManager, DownloadTracker
14+
from spotdl.providers.provider_utils import _get_converted_file_path
1415

1516

1617
class DownloadManager:
@@ -176,7 +177,6 @@ async def download_song(self, song_object: SongObject) -> None:
176177
"outtmpl": f"{str(temp_folder)}/%(id)s.%(ext)s",
177178
"quiet": True,
178179
"no_warnings": True,
179-
"ignoreerrors": True,
180180
"logger": YTDLLogger(),
181181
"progress_hooks": [display_progress_tracker.ytdl_progress_hook]
182182
if display_progress_tracker
@@ -276,81 +276,8 @@ def _perform_audio_download(
276276
except Exception as e: # noqa:E722
277277
# ! This is equivalent to a failed download, we do nothing, the song remains on
278278
# ! download_trackers download queue and all is well...
279-
280279
temp_files = Path(temp_folder).glob(f"{converted_file_name}.*")
281280
for temp_file in temp_files:
282281
temp_file.unlink()
283282

284283
raise e
285-
286-
287-
# ========================
288-
# === Helper function ===
289-
# ========================
290-
291-
292-
def _sanitize_filename(input_str: str) -> str:
293-
output = input_str
294-
295-
# ! this is windows specific (disallowed chars)
296-
output = "".join(char for char in output if char not in "/?\\*|<>")
297-
298-
# ! double quotes (") and semi-colons (:) are also disallowed characters but we would
299-
# ! like to retain their equivalents, so they aren't removed in the prior loop
300-
output = output.replace('"', "'").replace(":", "-")
301-
302-
return output
303-
304-
305-
def _get_smaller_file_path(input_song: SongObject, output_format: str) -> Path:
306-
# Only use the first artist if the song path turns out to be too long
307-
smaller_name = f"{input_song.contributing_artists[0]} - {input_song.song_name}"
308-
309-
smaller_name = _sanitize_filename(smaller_name)
310-
311-
try:
312-
return Path(f"{smaller_name}.{output_format}").resolve()
313-
except (OSError, WindowsError):
314-
# Expected to happen in the rare case when the saved path is too long,
315-
# even with the short filename
316-
raise OSError("Cannot save song due to path issues.")
317-
318-
319-
def _get_converted_file_path(song_obj: SongObject, output_format: str = None) -> Path:
320-
321-
# ! we eliminate contributing artist names that are also in the song name, else we
322-
# ! would end up with things like 'Jetta, Mastubs - I'd love to change the world
323-
# ! (Mastubs REMIX).mp3' which is kinda an odd file name.
324-
325-
# also make sure that main artist is included in artistStr even if they
326-
# are in the song name, for example
327-
# Lil Baby - Never Recover (Lil Baby & Gunna, Drake).mp3
328-
329-
artists_filtered = []
330-
331-
if output_format is None:
332-
output_format = "mp3"
333-
334-
for artist in song_obj.contributing_artists:
335-
if artist.lower() not in song_obj.song_name:
336-
artists_filtered.append(artist)
337-
elif artist.lower() is song_obj.contributing_artists[0].lower():
338-
artists_filtered.append(artist)
339-
340-
artist_str = ", ".join(artists_filtered)
341-
342-
converted_file_name = _sanitize_filename(
343-
f"{artist_str} - {song_obj.song_name}.{output_format}"
344-
)
345-
346-
converted_file_path = Path(converted_file_name)
347-
348-
# ! Checks if a file name is too long (256 max on both linux and windows)
349-
try:
350-
if len(str(converted_file_path.resolve().name)) > 256:
351-
print("Path was too long. Using Small Path.")
352-
return _get_smaller_file_path(song_obj, output_format)
353-
except (OSError, WindowsError):
354-
return _get_smaller_file_path(song_obj, output_format)
355-
356-
return converted_file_path

spotdl/download/ffmpeg.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ async def convert(
102102

103103
proc_out = await process.communicate()
104104

105-
if proc_out[0] and proc_out[1]:
105+
if proc_out[0] or proc_out[1]:
106106
out = str(b"".join(proc_out))
107107
else:
108108
out = ""

spotdl/download/progress_ui_handler.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ def warning(self, msg):
4242
pass
4343

4444
def error(self, msg):
45-
pass
45+
raise Exception(msg)
4646

4747

4848
class SizedTextColumn(ProgressColumn):

spotdl/parsers/query_parser.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,15 @@ def parse_query(
2626
# linefeed to visually separate output for each query
2727
print()
2828

29-
return songs_list
29+
# remove duplicates
30+
seen_songs = set()
31+
songs = []
32+
for song in songs_list:
33+
if song.file_name not in seen_songs:
34+
songs.append(song)
35+
seen_songs.add(song.file_name)
36+
37+
return songs
3038

3139

3240
def parse_request(
@@ -64,7 +72,7 @@ def parse_request(
6472
elif "open.spotify.com" in request and "album" in request:
6573
print("Fetching Album...")
6674
song_list = song_gatherer.from_album(
67-
request, output_format, use_youtube, threads
75+
request, output_format, use_youtube, generate_m3u, threads
6876
)
6977
elif "open.spotify.com" in request and "playlist" in request:
7078
print("Fetching Playlist...")

spotdl/providers/provider_utils.py

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from typing import List
44
from rapidfuzz import fuzz
55
from bs4 import BeautifulSoup
6+
from pathlib import Path
67

78

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

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

6566

6667
def _get_song_lyrics(song_name: str, song_artists: List[str]) -> str:
@@ -102,3 +103,70 @@ def _get_song_lyrics(song_name: str, song_artists: List[str]) -> str:
102103
return ""
103104
except: # noqa: E722
104105
return ""
106+
107+
108+
def _sanitize_filename(input_str: str) -> str:
109+
output = input_str
110+
111+
# ! this is windows specific (disallowed chars)
112+
output = "".join(char for char in output if char not in "/?\\*|<>")
113+
114+
# ! double quotes (") and semi-colons (:) are also disallowed characters but we would
115+
# ! like to retain their equivalents, so they aren't removed in the prior loop
116+
output = output.replace('"', "'").replace(":", "-")
117+
118+
return output
119+
120+
121+
def _get_smaller_file_path(input_song, output_format: str) -> Path:
122+
# Only use the first artist if the song path turns out to be too long
123+
smaller_name = f"{input_song.contributing_artists[0]} - {input_song.song_name}"
124+
125+
smaller_name = _sanitize_filename(smaller_name)
126+
127+
try:
128+
return Path(f"{smaller_name}.{output_format}").resolve()
129+
except (OSError, WindowsError):
130+
# Expected to happen in the rare case when the saved path is too long,
131+
# even with the short filename
132+
raise OSError("Cannot save song due to path issues.")
133+
134+
135+
def _get_converted_file_path(song_obj, output_format: str = None) -> Path:
136+
137+
# ! we eliminate contributing artist names that are also in the song name, else we
138+
# ! would end up with things like 'Jetta, Mastubs - I'd love to change the world
139+
# ! (Mastubs REMIX).mp3' which is kinda an odd file name.
140+
141+
# also make sure that main artist is included in artistStr even if they
142+
# are in the song name, for example
143+
# Lil Baby - Never Recover (Lil Baby & Gunna, Drake).mp3
144+
145+
artists_filtered = []
146+
147+
if output_format is None:
148+
output_format = "mp3"
149+
150+
for artist in song_obj.contributing_artists:
151+
if artist.lower() not in song_obj.song_name:
152+
artists_filtered.append(artist)
153+
elif artist.lower() is song_obj.contributing_artists[0].lower():
154+
artists_filtered.append(artist)
155+
156+
artist_str = ", ".join(artists_filtered)
157+
158+
converted_file_name = _sanitize_filename(
159+
f"{artist_str} - {song_obj.song_name}.{output_format}"
160+
)
161+
162+
converted_file_path = Path(converted_file_name)
163+
164+
# ! Checks if a file name is too long (256 max on both linux and windows)
165+
try:
166+
if len(str(converted_file_path.resolve().name)) > 256:
167+
print("Path was too long. Using Small Path.")
168+
return _get_smaller_file_path(song_obj, output_format)
169+
except (OSError, WindowsError):
170+
return _get_smaller_file_path(song_obj, output_format)
171+
172+
return converted_file_path

spotdl/providers/yt_provider.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ def search_and_get_best_match(
4545
if isrc_result is not None and isrc_result.watch_url is not None:
4646
return isrc_result.watch_url
4747

48-
song_title = _create_song_title(song_name, song_artists)
48+
song_title = _create_song_title(song_name, song_artists).lower()
4949

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

128128
artist_match = (artist_match_number / len(song_artists)) * 100
129-
song_title = _create_song_title(song_name, song_artists)
129+
song_title = _create_song_title(song_name, song_artists).lower()
130130
name_match = round(
131131
_match_percentage(
132132
unidecode(result.title.lower()), unidecode(song_title), 60

spotdl/providers/ytm_provider.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ def search_and_get_best_match(
6161
):
6262
return isrc_result["link"]
6363

64-
song_title = _create_song_title(song_name, song_artists)
64+
song_title = _create_song_title(song_name, song_artists).lower()
6565

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

9191
# Order video results
@@ -194,7 +194,7 @@ def _order_ytm_results(
194194

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

197-
song_title = _create_song_title(song_name, song_artists)
197+
song_title = _create_song_title(song_name, song_artists).lower()
198198

199199
# Find name match and drop results below 60%
200200
# this needs more testing

0 commit comments

Comments
 (0)