added download-archive feature (#232)

master
Justin Mai 2017-12-24 02:35:27 -08:00 committed by Ronan
parent ebf66a278c
commit d95a99cc5e
2 changed files with 147 additions and 82 deletions

View File

@ -45,36 +45,40 @@ scdl -l https://soundcloud.com/jumpstreetpsy/low-extender
# Download one playlist
scdl -l https://soundcloud.com/pandadub/sets/the-lost-ship
# Download only new tracks from a playlist
scdl -l https://soundcloud.com/pandadub/sets/the-lost-ship --download-archive archive.txt
# Download your likes (with authentification token)
scdl me -f
```
### Options:
```
-h --help Show this screen
--version Show version
me Use the user profile from the auth_token
-l [url] URL can be track/playlist/user
-s Download the stream of a user (token needed)
-a Download all tracks of a user (including reposts)
-t Download all uploads of a user (no reposts)
-f Download all favorites of a user
-C Download all commented by a user
-p Download all playlists of a user
-m Download all liked and owned playlists of a user
-c Continue if a downloaded file already exists
-o [offset] Begin with a custom offset
--path [path] Use a custom path for downloaded files
--min-size [min-size] Skip tracks smaller than size (k/m/g)
--max-size [max-size] Skip tracks larger than size (k/m/g)
--hidewarnings Hide Warnings. (use with precaution)
--addtofile Add the artist name to the filename if it isn't in the filename already
--addtimestamp Adds the timestamp of the creation of the track to the title (useful to sort chronologically)
--onlymp3 Download only the mp3 file even if the track is Downloadable
--error Set log level to ERROR
--debug Set log level to DEBUG
--hide-progress Hide the wget progress bar
--no-playlist-folder Download playlist tracks into directory, instead of making a playlist subfolder (the default)
-h --help Show this screen
--version Show version
me Use the user profile from the auth_token
-l [url] URL can be track/playlist/user
-s Download the stream of a user (token needed)
-a Download all tracks of a user (including reposts)
-t Download all uploads of a user (no reposts)
-f Download all favorites of a user
-C Download all commented by a user
-p Download all playlists of a user
-m Download all liked and owned playlists of a user
-c Continue if a downloaded file already exists
-o [offset] Begin with a custom offset
--path [path] Use a custom path for downloaded files
--min-size [min-size] Skip tracks smaller than size (k/m/g)
--max-size [max-size] Skip tracks larger than size (k/m/g)
--hidewarnings Hide Warnings. (use with precaution)
--addtofile Add the artist name to the filename if it isn't in the filename already
--addtimestamp Adds the timestamp of the creation of the track to the title (useful to sort chronologically)
--onlymp3 Download only the mp3 file even if the track is Downloadable
--error Set log level to ERROR
--debug Set log level to DEBUG
--hide-progress Hide the wget progress bar
--no-playlist-folder Download playlist tracks into directory, instead of making a playlist subfolder (the default)
--download-archive [file] Keep track of track IDs in an archive file and skip already-downloaded files
```
@ -85,6 +89,7 @@ scdl me -f
* Download all songs from one playlist
* Download all songs from all playlists from a user
* Download all songs from a user's favorites
* Download only new tracks from a list (playlist, favorites, etc.)
* Set the tags with mutagen (Title / Artist / Album / Artwork)
* Create playlist files when downloading a playlist

View File

@ -6,10 +6,10 @@
Usage:
scdl -l <track_url> [-a | -f | -C | -t | -p][-c][-o <offset>]\
[--hidewarnings][--debug | --error][--path <path>][--addtofile][--addtimestamp][--onlymp3]
[--hide-progress][--min-size <size>][--max-size <size>][--remove][--no-playlist-folder]
[--hide-progress][--min-size <size>][--max-size <size>][--remove][--no-playlist-folder][--download-archive <file>]
scdl me (-s | -a | -f | -t | -p | -m)[-c][-o <offset>]\
[--hidewarnings][--debug | --error][--path <path>][--addtofile][--addtimestamp][--onlymp3]
[--hide-progress][--min-size <size>][--max-size <size>][--remove][--no-playlist-folder]
[--hide-progress][--min-size <size>][--max-size <size>][--remove][--no-playlist-folder][--download-archive <file>]
scdl -h | --help
scdl --version
@ -40,6 +40,7 @@ Options:
--debug Set log level to DEBUG
--hide-progress Hide the wget progress bar
--no-playlist-folder Download playlist tracks into directory, instead of making a playlist subfolder (the default)
--download-archive [file] Keep track of track IDs in an archive file and skip already-downloaded files
"""
import logging
@ -393,9 +394,11 @@ def try_utime(path, filetime):
def get_filename(track, title, is_original = False):
invalid_chars = '\/:*?|<>"'
username = track['user']['username']
if arguments['--addtofile']:
if username not in title and '-' not in title:
title = '{0} - {1}'.format(username, title)
logger.debug('Adding "{0}" to filename'.format(username))
if arguments['--addtimestamp']:
# created_at sample: 2017/03/03 09:29:33 +0000
@ -421,15 +424,15 @@ def download_track(track, playlist_name=None, playlist_file=None):
title = track['title']
title = title.encode('utf-8', 'ignore').decode('utf8')
if track['streamable']:
url = track['stream_url']
else:
logger.error('{0} is not streamable...'.format(title))
return
logger.info('Downloading {0}'.format(title))
# Not streamable
if not track['streamable']:
logger.error('{0} is not streamable...'.format(title))
return
r = None
# filename
# Downloadable track
if track['downloadable'] and not arguments['--onlymp3']:
logger.info('Downloading the original file.')
original_url = track['download_url']
@ -446,9 +449,11 @@ def download_track(track, playlist_name=None, playlist_file=None):
filename = get_filename(track, title)
else:
filename = get_filename(track, title)
logger.debug("filename : {0}".format(filename))
# Skip if file ID or filename already exists
if already_downloaded(track, title, filename): return
# Add the track to the generated m3u playlist file
if playlist_file:
duration = math.floor(track['duration'] / 1000)
@ -461,64 +466,119 @@ def download_track(track, playlist_name=None, playlist_file=None):
if arguments['--remove']:
fileToKeep.append(filename)
# Download
if not os.path.isfile(filename):
if r is None or r.status_code == 401:
r = requests.get(url, params={'client_id': CLIENT_ID}, stream=True)
# Streamable track download
if r is None or r.status_code == 401:
url = track['stream_url']
r = requests.get(url, params={'client_id': CLIENT_ID}, stream=True)
logger.debug(r.url)
if r.status_code == 401 or r.status_code == 429:
r = requests.get(url, params={'client_id': ALT_CLIENT_ID}, stream=True)
logger.debug(r.url)
if r.status_code == 401 or r.status_code == 429:
r = requests.get(url, params={'client_id': ALT_CLIENT_ID}, stream=True)
logger.debug(r.url)
r.raise_for_status()
temp = tempfile.NamedTemporaryFile(delete=False)
r.raise_for_status()
temp = tempfile.NamedTemporaryFile(delete=False)
total_length = int(r.headers.get('content-length'))
total_length = int(r.headers.get('content-length'))
min_size = arguments.get('--min-size')
max_size = arguments.get('--max-size')
min_size = arguments.get('--min-size')
max_size = arguments.get('--max-size')
if min_size is not None and total_length < min_size:
logging.info('{0} not large enough, skipping'.format(title))
return
if min_size is not None and total_length < min_size:
logging.info('{0} not large enough, skipping'.format(title))
return
if max_size is not None and total_length > max_size:
logging.info('{0} too large, skipping'.format(title))
return
if max_size is not None and total_length > max_size:
logging.info('{0} too large, skipping'.format(title))
return
with temp as f:
for chunk in progress.bar(
r.iter_content(chunk_size=1024),
expected_size=(total_length/1024) + 1,
hide=True if arguments["--hide-progress"] else False
):
if chunk:
f.write(chunk)
f.flush()
shutil.move(temp.name, os.path.join(os.getcwd(), filename))
if filename.endswith('.mp3') or filename.endswith('.m4a'):
try:
set_metadata(track, filename, playlist_name)
except Exception as e:
logger.error('Error trying to set the tags...')
logger.debug(e)
else:
logger.error("This type of audio doesn't support tagging...")
#Try to change the real creation date
created_at = track['created_at']
filetime = int(time.mktime(datetime.strptime(created_at, '%Y/%m/%d %H:%M:%S %z').timetuple()))
try_utime(filename,filetime)
with temp as f:
for chunk in progress.bar(
r.iter_content(chunk_size=1024),
expected_size=(total_length/1024) + 1,
hide=True if arguments["--hide-progress"] else False
):
if chunk:
f.write(chunk)
f.flush()
shutil.move(temp.name, os.path.join(os.getcwd(), filename))
if filename.endswith('.mp3') or filename.endswith('.m4a'):
try:
set_metadata(track, filename, playlist_name)
except Exception as e:
logger.error('Error trying to set the tags...')
logger.debug(e)
else:
if arguments['-c'] or arguments['--remove']:
logger.info('{0} already Downloaded'.format(title))
return
else:
logger.error('Music already exists ! (use -c to continue)')
sys.exit(0)
logger.error("This type of audio doesn't support tagging...")
#Try to change the real creation date
created_at = track['created_at']
filetime = int(time.mktime(datetime.strptime(created_at, '%Y/%m/%d %H:%M:%S %z').timetuple()))
try_utime(filename,filetime)
logger.info('{0} Downloaded.\n'.format(filename))
record_download_archive(track)
def already_downloaded(track, title, filename=None):
"""
Returns True if the file has already been downloaded
"""
global arguments
already_downloaded = False
if filename and os.path.isfile(filename):
already_downloaded = True
if arguments['--download-archive'] and in_download_archive(track):
already_downloaded = True
if already_downloaded:
if arguments['-c'] or arguments['--remove']:
logger.info('Track "{0}" already downloaded.'.format(title))
return True
else:
logger.error('Track "{0}" already exists! Exiting... (run again with -c to continue)'.format(title))
sys.exit(0)
return False
def in_download_archive(track):
"""
Returns True if a track_id exists in the download archive
"""
global arguments
if not arguments['--download-archive']: return
archive_filename = arguments.get('--download-archive')
try:
with open(archive_filename, 'a+', encoding='utf-8') as file:
logger.debug('Contents of {0}:'.format(archive_filename))
file.seek(0)
track_id = '{0}'.format(track['id'])
for line in file:
logger.debug('"'+line.strip()+'"')
if line.strip() == track_id:
return True
except IOError as ioe:
logger.error('Error trying to read download archive...')
logger.debug(ioe)
return False
def record_download_archive(track):
"""
Write the track_id in the download archive
"""
global arguments
if not arguments['--download-archive']: return
archive_filename = arguments.get('--download-archive')
try:
with open(archive_filename, 'a', encoding='utf-8') as file:
file.write('{0}'.format(track['id'])+'\n')
except IOError as ioe:
logger.error('Error trying to write to download archive...')
logger.debug(ioe)
def set_metadata(track, filename, album=None):