Better error handling, return exit code 1 on errors
parent
d79d55ff89
commit
0f6991077b
|
@ -2,4 +2,4 @@
|
|||
|
||||
"""Python Soundcloud Music Downloader."""
|
||||
|
||||
__version__ = "v2.2.4"
|
||||
__version__ = "v2.2.5"
|
93
scdl/scdl.py
93
scdl/scdl.py
|
@ -8,7 +8,7 @@ Usage:
|
|||
[-o <offset>][--hidewarnings][--debug | --error][--path <path>][--addtofile][--addtimestamp]
|
||||
[--onlymp3][--hide-progress][--min-size <size>][--max-size <size>][--remove][--no-album-tag]
|
||||
[--no-playlist-folder][--download-archive <file>][--extract-artist][--flac][--original-art]
|
||||
[--original-name][--no-original][--only-original][--name-format <format>][--strict]
|
||||
[--original-name][--no-original][--only-original][--name-format <format>][--strict-playlist]
|
||||
[--playlist-name-format <format>][--client-id <id>][--auth-token <token>][--overwrite]
|
||||
scdl -h | --help
|
||||
scdl --version
|
||||
|
@ -58,7 +58,7 @@ Options:
|
|||
--client-id [id] Specify the client_id to use
|
||||
--auth-token [token] Specify the auth token to use
|
||||
--overwrite Overwrite file if it already exists
|
||||
--strict Fail if setting metadata fails
|
||||
--strict-playlist Abort playlist downloading if one track fails to download
|
||||
"""
|
||||
|
||||
import configparser
|
||||
|
@ -71,11 +71,11 @@ mimetypes.init()
|
|||
import os
|
||||
import re
|
||||
import shutil
|
||||
import signal
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
import traceback
|
||||
import warnings
|
||||
from dataclasses import asdict
|
||||
|
||||
|
@ -100,12 +100,22 @@ logger.addFilter(utils.ColorizeFilter())
|
|||
|
||||
fileToKeep = []
|
||||
|
||||
class SoundCloudException(Exception):
|
||||
pass
|
||||
|
||||
def handle_exception(exc_type, exc_value, exc_traceback):
|
||||
if issubclass(exc_type, KeyboardInterrupt):
|
||||
logger.error("\nGoodbye!")
|
||||
else:
|
||||
logger.error("".join(traceback.format_exception(exc_type, exc_value, exc_traceback)))
|
||||
sys.exit(1)
|
||||
|
||||
sys.excepthook = handle_exception
|
||||
|
||||
def main():
|
||||
"""
|
||||
Main function, parses the URL from command line arguments
|
||||
"""
|
||||
signal.signal(signal.SIGINT, signal_handler)
|
||||
|
||||
# exit if ffmpeg not installed
|
||||
if not is_ffmpeg_available():
|
||||
|
@ -153,10 +163,12 @@ def main():
|
|||
client = SoundCloud(client_id, token if token else None)
|
||||
|
||||
if not client.is_client_id_valid():
|
||||
raise ValueError(f"client_id is not valid")
|
||||
logger.error(f"Invalid client_id in {config_file}")
|
||||
sys.exit(1)
|
||||
|
||||
if (token or arguments["me"]) and not client.is_auth_token_valid():
|
||||
raise ValueError(f"auth_token is not valid")
|
||||
logger.error(f"Invalid auth_token in {config_file}")
|
||||
sys.exit(1)
|
||||
|
||||
if arguments["-o"] is not None:
|
||||
try:
|
||||
|
@ -249,7 +261,8 @@ def download_url(client: SoundCloud, **kwargs):
|
|||
item = client.resolve(url)
|
||||
logger.debug(item)
|
||||
if not item:
|
||||
return
|
||||
logger.error("URL is not valid")
|
||||
sys.exit(1)
|
||||
elif item.kind == "track":
|
||||
logger.info("Found a track")
|
||||
download_track(client, item, **kwargs)
|
||||
|
@ -265,25 +278,27 @@ def download_url(client: SoundCloud, **kwargs):
|
|||
for i, like in enumerate(resources, 1):
|
||||
logger.info(f"like n°{i} of {user.likes_count}")
|
||||
if hasattr(like, "track"):
|
||||
download_track(client, like.track, **kwargs)
|
||||
download_track(client, like.track, exit_on_fail=kwargs.get("strict_playlist"), **kwargs)
|
||||
elif hasattr(like, "playlist"):
|
||||
download_playlist(client, client.get_playlist(like.playlist.id), **kwargs)
|
||||
else:
|
||||
raise ValueError(f"Unknown like type {like}")
|
||||
logger.error(f"Unknown like type {like}")
|
||||
if kwargs.get("strict_playlist"):
|
||||
sys.exit(1)
|
||||
logger.info(f"Downloaded all likes of user {user.username}!")
|
||||
elif kwargs.get("C"):
|
||||
logger.info(f"Retrieving all commented tracks of user {user.username}...")
|
||||
resources = client.get_user_comments(user.id, limit=1000)
|
||||
for i, comment in enumerate(resources, 1):
|
||||
logger.info(f"comment n°{i} of {user.comments_count}")
|
||||
download_track(client, client.get_track(comment.track.id), **kwargs)
|
||||
download_track(client, client.get_track(comment.track.id), exit_on_fail=kwargs.get("strict_playlist"), **kwargs)
|
||||
logger.info(f"Downloaded all commented tracks of user {user.username}!")
|
||||
elif kwargs.get("t"):
|
||||
logger.info(f"Retrieving all tracks of user {user.username}...")
|
||||
resources = client.get_user_tracks(user.id, limit=1000)
|
||||
for i, track in enumerate(resources, 1):
|
||||
logger.info(f"track n°{i} of {user.track_count}")
|
||||
download_track(client, track, **kwargs)
|
||||
download_track(client, track, exit_on_fail=kwargs.get("strict_playlist"), **kwargs)
|
||||
logger.info(f"Downloaded all tracks of user {user.username}!")
|
||||
elif kwargs.get("a"):
|
||||
logger.info(f"Retrieving all tracks & reposts of user {user.username}...")
|
||||
|
@ -291,11 +306,13 @@ def download_url(client: SoundCloud, **kwargs):
|
|||
for i, item in enumerate(resources, 1):
|
||||
logger.info(f"item n°{i} of {user.track_count + user.reposts_count if user.reposts_count else '?'}")
|
||||
if item.type in ("track", "track-repost"):
|
||||
download_track(client, item.track, **kwargs)
|
||||
download_track(client, item.track, exit_on_fail=kwargs.get("strict_playlist"), **kwargs)
|
||||
elif item.type in ("playlist", "playlist-repost"):
|
||||
download_playlist(client, item.playlist, **kwargs)
|
||||
else:
|
||||
raise ValueError(f"Unknown item type {item.type}")
|
||||
logger.error(f"Unknown item type {item.type}")
|
||||
if kwargs.get("strict_playlist"):
|
||||
sys.exit(1)
|
||||
logger.info(f"Downloaded all tracks & reposts of user {user.username}!")
|
||||
elif kwargs.get("p"):
|
||||
logger.info(f"Retrieving all playlists of user {user.username}...")
|
||||
|
@ -310,16 +327,20 @@ def download_url(client: SoundCloud, **kwargs):
|
|||
for i, item in enumerate(resources, 1):
|
||||
logger.info(f"item n°{i} of {user.reposts_count or '?'}")
|
||||
if item.type == "track-repost":
|
||||
download_track(client, item.track, **kwargs)
|
||||
download_track(client, item.track, exit_on_fail=kwargs.get("strict_playlist"), **kwargs)
|
||||
elif item.type == "playlist-repost":
|
||||
download_playlist(client, item.playlist, **kwargs)
|
||||
else:
|
||||
raise ValueError(f"Unknown item type {item.type}")
|
||||
logger.error(f"Unknown item type {item.type}")
|
||||
if kwargs.get("strict_playlist"):
|
||||
sys.exit(1)
|
||||
logger.info(f"Downloaded all reposts of user {user.username}!")
|
||||
else:
|
||||
logger.error("Please provide a download type...")
|
||||
sys.exit(1)
|
||||
else:
|
||||
logger.error("Unknown item type {0}".format(item.kind))
|
||||
logger.error(f"Unknown item type {item.kind}")
|
||||
sys.exit(1)
|
||||
|
||||
def remove_files():
|
||||
"""
|
||||
|
@ -363,7 +384,7 @@ def download_playlist(client: SoundCloud, playlist: BasicAlbumPlaylist, **kwargs
|
|||
}
|
||||
if isinstance(track, MiniTrack):
|
||||
track = client.get_track(track.id)
|
||||
download_track(client, track, playlist_info, **kwargs)
|
||||
download_track(client, track, playlist_info, kwargs.get("strict_playlist"), **kwargs)
|
||||
finally:
|
||||
if not kwargs.get("no_playlist_folder"):
|
||||
os.chdir("..")
|
||||
|
@ -497,6 +518,9 @@ def get_track_m3u8(client: SoundCloud, track: BasicTrack, aac=False):
|
|||
|
||||
def download_hls(client: SoundCloud, track: BasicTrack, title: str, playlist_info=None, **kwargs):
|
||||
|
||||
if not track.media.transcodings:
|
||||
raise SoundCloudException(f"Track {track.permalink_url} has no transcodings available")
|
||||
|
||||
if kwargs["onlymp3"]:
|
||||
aac = False
|
||||
else:
|
||||
|
@ -524,23 +548,22 @@ def download_hls(client: SoundCloud, track: BasicTrack, title: str, playlist_inf
|
|||
return (filename, False)
|
||||
|
||||
|
||||
def download_track(client: SoundCloud, track: BasicTrack, playlist_info=None, **kwargs):
|
||||
def download_track(client: SoundCloud, track: BasicTrack, playlist_info=None, exit_on_fail=True, **kwargs):
|
||||
"""
|
||||
Downloads a track
|
||||
"""
|
||||
try:
|
||||
title = track.title
|
||||
title = title.encode("utf-8", "ignore").decode("utf8")
|
||||
logger.info(f"Downloading {title}")
|
||||
|
||||
# Not streamable
|
||||
if not track.streamable:
|
||||
logger.error(f"{title} is not streamable...")
|
||||
return
|
||||
raise SoundCloudException(f"{title} is not streamable...")
|
||||
|
||||
# Geoblocked track
|
||||
if track.policy == "BLOCK":
|
||||
logger.error(f"{title} is not available in your location...\n")
|
||||
return
|
||||
raise SoundCloudException(f"{title} is not available in your location...")
|
||||
|
||||
# Downloadable track
|
||||
filename = None
|
||||
|
@ -555,8 +578,7 @@ def download_track(client: SoundCloud, track: BasicTrack, playlist_info=None, **
|
|||
|
||||
if filename is None:
|
||||
if kwargs.get("only_original"):
|
||||
logger.info(f'Track "{title}" does not have original file available. Skipping...')
|
||||
return
|
||||
raise SoundCloudException(f'Track "{track.permalink_url}" does not have original file available. Not downloading...')
|
||||
filename, is_already_downloaded = download_hls(client, track, title, playlist_info, **kwargs)
|
||||
|
||||
if kwargs.get("remove"):
|
||||
|
@ -566,14 +588,11 @@ def download_track(client: SoundCloud, track: BasicTrack, playlist_info=None, **
|
|||
|
||||
# Skip if file ID or filename already exists
|
||||
if is_already_downloaded and not kwargs.get("force_metadata"):
|
||||
logger.info(f'Track "{title}" already downloaded.')
|
||||
return
|
||||
raise SoundCloudException(f"{filename} already downloaded.")
|
||||
|
||||
# If file does not exist an error occurred
|
||||
if not os.path.isfile(filename):
|
||||
logger.error(f"An error occurred downloading {filename}.\n")
|
||||
logger.error("Exiting...")
|
||||
sys.exit(1)
|
||||
raise SoundCloudException(f"An error occurred downloading {filename}.")
|
||||
|
||||
# Try to set the metadata
|
||||
if (
|
||||
|
@ -584,10 +603,9 @@ def download_track(client: SoundCloud, track: BasicTrack, playlist_info=None, **
|
|||
try:
|
||||
set_metadata(track, filename, playlist_info, **kwargs)
|
||||
except:
|
||||
logger.exception("Error trying to set the tags...")
|
||||
if kwargs.get("strict"):
|
||||
os.remove(filename)
|
||||
sys.exit(1)
|
||||
logger.exception("Error trying to set the tags...")
|
||||
raise SoundCloudException("Error trying to set the tags...")
|
||||
else:
|
||||
logger.error("This type of audio doesn't support tagging...")
|
||||
|
||||
|
@ -596,6 +614,10 @@ def download_track(client: SoundCloud, track: BasicTrack, playlist_info=None, **
|
|||
try_utime(filename, filetime)
|
||||
|
||||
logger.info(f"{filename} Downloaded.\n")
|
||||
except SoundCloudException as err:
|
||||
logger.error(err)
|
||||
if exit_on_fail:
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def can_convert(filename):
|
||||
|
@ -777,13 +799,6 @@ def limit_filename_length(name: str, ext: str, max_bytes=255):
|
|||
name = name[:-1]
|
||||
return name + ext
|
||||
|
||||
def signal_handler(signal, frame):
|
||||
"""
|
||||
Handle keyboard interrupt
|
||||
"""
|
||||
logger.info("\nGood bye!")
|
||||
sys.exit(0)
|
||||
|
||||
def is_ffmpeg_available():
|
||||
"""
|
||||
Returns true if ffmpeg is available in the operating system
|
||||
|
|
Loading…
Reference in New Issue