2015-05-14 00:41:45 -07:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
# -*- encoding: utf-8 -*-
|
|
|
|
|
2014-10-20 13:23:46 -07:00
|
|
|
"""scdl allow you to download music from soundcloud
|
|
|
|
|
2014-10-14 10:43:30 -07:00
|
|
|
Usage:
|
2015-01-14 08:55:14 -08:00
|
|
|
scdl -l <track_url> [-a | -f | -t | -p][-c][-o <offset>]\
|
2016-02-08 05:32:59 -08:00
|
|
|
[--hidewarnings][--debug | --error][--path <path>][--addtofile][--onlymp3]
|
|
|
|
[--hide-progress]
|
2015-01-14 08:55:14 -08:00
|
|
|
scdl me (-s | -a | -f | -t | -p)[-c][-o <offset>]\
|
2016-02-08 05:32:59 -08:00
|
|
|
[--hidewarnings][--debug | --error][--path <path>][--addtofile][--onlymp3]
|
|
|
|
[--hide-progress]
|
2015-01-14 08:55:14 -08:00
|
|
|
scdl -h | --help
|
|
|
|
scdl --version
|
2014-10-14 10:43:30 -07:00
|
|
|
|
2014-10-20 13:23:46 -07:00
|
|
|
|
2014-10-14 10:43:30 -07:00
|
|
|
Options:
|
2014-11-26 10:45:28 -08:00
|
|
|
-h --help Show this screen
|
|
|
|
--version Show version
|
2014-11-16 09:19:42 -08:00
|
|
|
me Use the user profile from the auth_token
|
2014-11-26 10:45:28 -08:00
|
|
|
-l [url] URL can be track/playlist/user
|
2016-01-10 04:49:09 -08:00
|
|
|
-s Download the stream of a user (token needed)
|
|
|
|
-a Download all tracks of a user (including repost)
|
|
|
|
-t Download all uploads of a user
|
|
|
|
-f Download all favorites of a user
|
|
|
|
-p Download all playlists of a user
|
2014-11-16 09:19:42 -08:00
|
|
|
-c Continue if a music already exist
|
2014-11-26 10:45:28 -08:00
|
|
|
-o [offset] Begin with a custom offset
|
|
|
|
--path [path] Use a custom path for this time
|
2014-11-16 09:19:42 -08:00
|
|
|
--hidewarnings Hide Warnings. (use with precaution)
|
|
|
|
--addtofile Add the artist name to the filename if it isn't in the filename already
|
2015-01-19 11:23:46 -08:00
|
|
|
--onlymp3 Download only the mp3 file even if the track is Downloadable
|
|
|
|
--error Only print debug information (Error/Warning)
|
|
|
|
--debug Print every information and
|
2015-08-24 18:38:00 -07:00
|
|
|
--hide-progress Hide the wget progress bar
|
2014-10-14 10:43:30 -07:00
|
|
|
"""
|
2015-05-09 04:01:49 -07:00
|
|
|
|
|
|
|
import logging
|
2014-10-20 13:23:46 -07:00
|
|
|
import os
|
|
|
|
import signal
|
|
|
|
import sys
|
2014-10-23 08:12:24 -07:00
|
|
|
import time
|
2015-05-09 04:10:15 -07:00
|
|
|
import warnings
|
2015-08-26 14:21:15 -07:00
|
|
|
import math
|
2016-02-07 17:12:43 -08:00
|
|
|
import shutil
|
|
|
|
import requests
|
2016-02-07 17:35:51 -08:00
|
|
|
import re
|
2016-03-01 14:12:34 -08:00
|
|
|
import tempfile
|
2015-01-19 13:11:55 -08:00
|
|
|
|
2015-05-09 04:10:15 -07:00
|
|
|
import configparser
|
|
|
|
import mutagen
|
|
|
|
from docopt import docopt
|
2016-02-07 17:12:43 -08:00
|
|
|
from clint.textui import progress
|
2014-11-12 08:00:27 -08:00
|
|
|
|
2015-05-09 04:01:49 -07:00
|
|
|
from scdl import __version__
|
2016-04-17 06:00:53 -07:00
|
|
|
from scdl import client, utils
|
2015-05-09 04:01:49 -07:00
|
|
|
|
2015-06-28 13:24:38 -07:00
|
|
|
logging.basicConfig(level=logging.INFO, format='%(message)s')
|
2016-01-31 05:29:57 -08:00
|
|
|
logging.getLogger('requests').setLevel(logging.WARNING)
|
2015-05-09 04:01:49 -07:00
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
logger.setLevel(logging.INFO)
|
2015-05-14 00:36:19 -07:00
|
|
|
logger.addFilter(utils.ColorizeFilter())
|
2015-05-09 06:35:18 -07:00
|
|
|
logger.newline = print
|
2015-05-09 04:01:49 -07:00
|
|
|
|
2015-01-14 08:55:14 -08:00
|
|
|
arguments = None
|
2014-10-20 13:23:46 -07:00
|
|
|
token = ''
|
2014-12-02 17:16:04 -08:00
|
|
|
path = ''
|
2014-11-16 09:19:42 -08:00
|
|
|
offset = 0
|
2015-04-28 06:47:17 -07:00
|
|
|
scdl_client_id = '95a4c0ef214f2a4a0852142807b54b35'
|
2016-04-17 05:32:04 -07:00
|
|
|
alternative_client_id = 'a3e059563d7fd3372b49b37f00a00bcf'
|
2015-01-19 13:11:55 -08:00
|
|
|
|
2016-02-29 09:08:41 -08:00
|
|
|
url = {
|
|
|
|
'favorites': ('https://api.soundcloud.com/users/{0}/favorites?'
|
|
|
|
'limit=200&offset={1}'),
|
|
|
|
'tracks': ('https://api.soundcloud.com/users/{0}/tracks?'
|
|
|
|
'limit=200&offset={1}'),
|
|
|
|
'all': ('https://api-v2.soundcloud.com/profile/soundcloud:users:{0}?'
|
|
|
|
'limit=200&offset={1}'),
|
|
|
|
'playlists': ('https://api.soundcloud.com/users/{0}/playlists?'
|
2016-03-01 14:58:59 -08:00
|
|
|
'limit=200&offset={1}'),
|
|
|
|
'resolve': ('https://api.soundcloud.com/resolve?url={0}'),
|
2016-04-17 06:00:53 -07:00
|
|
|
'user': ('https://api.soundcloud.com/users/{0}'),
|
2016-03-01 14:58:59 -08:00
|
|
|
'me': ('https://api.soundcloud.com/me?oauth_token={0}')
|
2016-02-29 09:08:41 -08:00
|
|
|
}
|
|
|
|
|
2016-04-17 06:00:53 -07:00
|
|
|
client = client.Client()
|
2014-10-22 10:29:56 -07:00
|
|
|
|
2014-10-12 15:16:18 -07:00
|
|
|
|
2014-10-14 10:43:30 -07:00
|
|
|
def main():
|
2014-11-16 09:19:42 -08:00
|
|
|
"""
|
|
|
|
Main function, call parse_url
|
|
|
|
"""
|
|
|
|
signal.signal(signal.SIGINT, signal_handler)
|
|
|
|
global offset
|
2015-01-14 08:55:14 -08:00
|
|
|
global arguments
|
2014-11-16 09:19:42 -08:00
|
|
|
|
|
|
|
# import conf file
|
|
|
|
get_config()
|
|
|
|
|
|
|
|
# Parse argument
|
2015-01-19 11:23:46 -08:00
|
|
|
arguments = docopt(__doc__, version=__version__)
|
2015-01-14 08:55:14 -08:00
|
|
|
|
2015-05-09 15:13:11 -07:00
|
|
|
if arguments['--debug']:
|
2015-05-09 04:01:49 -07:00
|
|
|
logger.level = logging.DEBUG
|
2015-05-09 15:13:11 -07:00
|
|
|
elif arguments['--error']:
|
2015-05-09 04:01:49 -07:00
|
|
|
logger.level = logging.ERROR
|
2015-01-14 08:55:14 -08:00
|
|
|
|
2015-05-09 15:13:11 -07:00
|
|
|
logger.info('Soundcloud Downloader')
|
2015-05-09 04:01:49 -07:00
|
|
|
logger.debug(arguments)
|
2014-11-26 10:45:28 -08:00
|
|
|
|
2015-05-09 15:13:11 -07:00
|
|
|
if arguments['-o'] is not None:
|
2014-11-16 09:19:42 -08:00
|
|
|
try:
|
2015-08-24 18:19:28 -07:00
|
|
|
offset = int(arguments['-o']) - 1
|
2014-11-16 09:19:42 -08:00
|
|
|
except:
|
2015-08-24 18:19:28 -07:00
|
|
|
logger.error('Offset should be an integer...')
|
2014-11-16 09:19:42 -08:00
|
|
|
sys.exit()
|
2015-08-24 18:19:28 -07:00
|
|
|
logger.debug('offset: %d', offset)
|
2014-11-16 09:19:42 -08:00
|
|
|
|
2015-05-09 15:13:11 -07:00
|
|
|
if arguments['--hidewarnings']:
|
|
|
|
warnings.filterwarnings('ignore')
|
2014-11-16 09:19:42 -08:00
|
|
|
|
2015-05-09 15:13:11 -07:00
|
|
|
if arguments['--path'] is not None:
|
|
|
|
if os.path.exists(arguments['--path']):
|
|
|
|
os.chdir(arguments['--path'])
|
2014-12-02 17:16:04 -08:00
|
|
|
else:
|
2015-05-09 04:01:49 -07:00
|
|
|
logger.error('Invalid path in arguments...')
|
2014-12-07 15:15:04 -08:00
|
|
|
sys.exit()
|
2015-05-09 04:01:49 -07:00
|
|
|
logger.debug('Downloading to '+os.getcwd()+'...')
|
2014-12-07 15:15:04 -08:00
|
|
|
|
2015-05-09 04:01:49 -07:00
|
|
|
logger.newline()
|
2015-05-09 15:13:11 -07:00
|
|
|
if arguments['-l']:
|
|
|
|
parse_url(arguments['-l'])
|
|
|
|
elif arguments['me']:
|
2016-02-29 09:08:41 -08:00
|
|
|
if arguments['-f']:
|
|
|
|
download(who_am_i(), 'favorites', 'likes')
|
2015-05-09 15:13:11 -07:00
|
|
|
elif arguments['-t']:
|
2016-02-29 09:08:41 -08:00
|
|
|
download(who_am_i(), 'tracks', 'uploaded tracks')
|
|
|
|
elif arguments['-a']:
|
|
|
|
download(who_am_i(), 'all', 'tracks and reposts')
|
2015-05-09 15:13:11 -07:00
|
|
|
elif arguments['-p']:
|
2016-02-29 09:08:41 -08:00
|
|
|
download(who_am_i(), 'playlists', 'playlists')
|
2014-10-22 10:29:56 -07:00
|
|
|
|
2014-10-14 10:43:30 -07:00
|
|
|
|
2014-10-20 13:23:46 -07:00
|
|
|
def get_config():
|
2014-11-16 09:19:42 -08:00
|
|
|
"""
|
|
|
|
read the path where to store music
|
|
|
|
"""
|
|
|
|
global token
|
|
|
|
config = configparser.ConfigParser()
|
|
|
|
config.read(os.path.join(os.path.expanduser('~'), '.config/scdl/scdl.cfg'))
|
|
|
|
try:
|
|
|
|
token = config['scdl']['auth_token']
|
|
|
|
path = config['scdl']['path']
|
|
|
|
except:
|
2015-05-09 04:01:49 -07:00
|
|
|
logger.error('Are you sure scdl.cfg is in $HOME/.config/scdl/ ?')
|
2014-11-16 09:19:42 -08:00
|
|
|
sys.exit()
|
|
|
|
if os.path.exists(path):
|
|
|
|
os.chdir(path)
|
|
|
|
else:
|
2015-05-09 04:01:49 -07:00
|
|
|
logger.error('Invalid path in scdl.cfg...')
|
2014-11-16 09:19:42 -08:00
|
|
|
sys.exit()
|
|
|
|
|
2014-10-20 13:23:46 -07:00
|
|
|
|
2016-04-17 05:32:04 -07:00
|
|
|
def get_item(track_url, client_id=scdl_client_id):
|
2014-11-16 09:19:42 -08:00
|
|
|
"""
|
|
|
|
Fetches metadata for an track or playlist
|
|
|
|
"""
|
|
|
|
try:
|
2016-03-01 14:58:59 -08:00
|
|
|
item_url = url['resolve'].format(track_url)
|
2016-04-17 05:32:04 -07:00
|
|
|
item_url = '{0}&client_id={1}'.format(item_url, client_id)
|
|
|
|
logger.debug(item_url)
|
|
|
|
|
2016-03-01 14:58:59 -08:00
|
|
|
r = requests.get(item_url)
|
2016-04-17 05:32:04 -07:00
|
|
|
if r.status_code == 403:
|
|
|
|
return get_item(track_url, alternative_client_id)
|
|
|
|
|
2016-03-01 14:58:59 -08:00
|
|
|
item = r.json()
|
2016-04-17 05:32:04 -07:00
|
|
|
no_tracks = item['kind'] == 'playlist' and not item['tracks']
|
2016-04-23 09:16:53 -07:00
|
|
|
if no_tracks and client_id != alternative_client_id:
|
2016-04-17 05:32:04 -07:00
|
|
|
return get_item(track_url, alternative_client_id)
|
2014-11-16 09:19:42 -08:00
|
|
|
except Exception:
|
2016-04-23 09:16:53 -07:00
|
|
|
if client_id == alternative_client_id:
|
|
|
|
logger.error('Get item failed...')
|
|
|
|
return
|
2015-05-09 04:01:49 -07:00
|
|
|
logger.error('Error resolving url, retrying...')
|
2014-11-26 10:56:53 -08:00
|
|
|
time.sleep(5)
|
|
|
|
try:
|
2016-04-17 05:32:04 -07:00
|
|
|
return get_item(track_url, alternative_client_id)
|
2014-11-26 10:56:53 -08:00
|
|
|
except Exception as e:
|
2015-05-09 15:13:11 -07:00
|
|
|
logger.error('Could not resolve url {0}'.format(track_url))
|
2015-05-09 04:01:49 -07:00
|
|
|
logger.exception(e)
|
2014-11-26 10:56:53 -08:00
|
|
|
sys.exit(0)
|
2014-11-16 09:19:42 -08:00
|
|
|
return item
|
2014-10-23 08:22:58 -07:00
|
|
|
|
|
|
|
|
|
|
|
def parse_url(track_url):
|
2014-11-16 09:19:42 -08:00
|
|
|
"""
|
2016-02-08 05:32:59 -08:00
|
|
|
Detects if the URL is a track or playlists, and parses the track(s)
|
|
|
|
to the track downloader
|
2014-11-16 09:19:42 -08:00
|
|
|
"""
|
2015-01-14 08:55:14 -08:00
|
|
|
global arguments
|
2014-11-16 09:19:42 -08:00
|
|
|
item = get_item(track_url)
|
2016-03-01 14:58:59 -08:00
|
|
|
logger.debug(item)
|
2014-11-16 09:19:42 -08:00
|
|
|
if not item:
|
|
|
|
return
|
2016-03-01 14:58:59 -08:00
|
|
|
elif item['kind'] == 'track':
|
2015-05-09 15:13:11 -07:00
|
|
|
logger.info('Found a track')
|
2016-03-01 14:58:59 -08:00
|
|
|
download_track(item)
|
|
|
|
elif item['kind'] == 'playlist':
|
2015-05-09 15:13:11 -07:00
|
|
|
logger.info('Found a playlist')
|
2016-03-01 14:58:59 -08:00
|
|
|
download_playlist(item)
|
|
|
|
elif item['kind'] == 'user':
|
2016-01-10 04:49:09 -08:00
|
|
|
logger.info('Found a user profile')
|
2015-05-09 15:13:11 -07:00
|
|
|
if arguments['-f']:
|
2016-03-01 13:27:47 -08:00
|
|
|
download(item, 'favorites', 'likes')
|
2015-05-09 15:13:11 -07:00
|
|
|
elif arguments['-t']:
|
2016-03-01 13:27:47 -08:00
|
|
|
download(item, 'tracks', 'uploaded tracks')
|
2015-05-09 15:13:11 -07:00
|
|
|
elif arguments['-a']:
|
2016-03-01 13:27:47 -08:00
|
|
|
download(item, 'all', 'tracks and reposts')
|
2015-05-09 15:13:11 -07:00
|
|
|
elif arguments['-p']:
|
2016-03-01 13:27:47 -08:00
|
|
|
download(item, 'playlists', 'playlists')
|
2014-11-16 09:19:42 -08:00
|
|
|
else:
|
2015-05-09 04:01:49 -07:00
|
|
|
logger.error('Please provide a download type...')
|
2014-11-16 09:19:42 -08:00
|
|
|
else:
|
2015-05-09 15:13:11 -07:00
|
|
|
logger.error('Unknown item type')
|
2014-11-16 09:19:42 -08:00
|
|
|
|
2014-10-23 08:22:58 -07:00
|
|
|
|
2014-10-23 07:14:29 -07:00
|
|
|
def who_am_i():
|
2014-11-16 09:19:42 -08:00
|
|
|
"""
|
|
|
|
display to who the current token correspond, check if the token is valid
|
|
|
|
"""
|
2016-03-01 14:58:59 -08:00
|
|
|
me = url['me'].format(token)
|
|
|
|
me = '{0}&client_id={1}'.format(me, scdl_client_id)
|
|
|
|
r = requests.get(me)
|
|
|
|
current_user = r.json()
|
|
|
|
logger.debug(me)
|
2014-11-16 09:19:42 -08:00
|
|
|
|
2016-03-01 14:58:59 -08:00
|
|
|
if r.status_code == 401:
|
2015-05-09 04:01:49 -07:00
|
|
|
logger.error('Invalid token...')
|
2014-11-16 09:19:42 -08:00
|
|
|
sys.exit(0)
|
2016-03-01 14:58:59 -08:00
|
|
|
logger.info('Hello {0}!'.format(current_user['username']))
|
2015-05-09 04:01:49 -07:00
|
|
|
logger.newline()
|
2014-11-16 09:19:42 -08:00
|
|
|
return current_user
|
|
|
|
|
2014-10-23 07:14:29 -07:00
|
|
|
|
2016-02-29 09:08:41 -08:00
|
|
|
def download(user, dl_type, name):
|
2014-11-16 09:19:42 -08:00
|
|
|
"""
|
2016-02-13 09:14:30 -08:00
|
|
|
Download all items of a user
|
2014-11-16 09:19:42 -08:00
|
|
|
"""
|
2016-02-13 09:14:30 -08:00
|
|
|
username = user['username']
|
2016-02-29 09:08:41 -08:00
|
|
|
user_id = user['id']
|
2016-02-08 05:32:59 -08:00
|
|
|
logger.info(
|
2016-02-13 09:14:30 -08:00
|
|
|
'Retrieving all the {0} of user {1}...'.format(name, username)
|
2016-01-31 05:15:27 -08:00
|
|
|
)
|
2016-02-29 09:08:41 -08:00
|
|
|
dl_url = url[dl_type].format(user_id, offset)
|
|
|
|
resources = client.get_collection(dl_url)
|
2015-08-24 18:19:28 -07:00
|
|
|
total = len(resources)
|
2016-02-13 09:14:30 -08:00
|
|
|
logger.info('Retrieved {0} {1}'.format(total, name))
|
2015-08-24 18:19:28 -07:00
|
|
|
for counter, item in enumerate(resources, 1):
|
|
|
|
try:
|
2016-02-13 09:14:30 -08:00
|
|
|
logger.debug(item)
|
2016-02-08 05:32:59 -08:00
|
|
|
logger.info('{0} n°{1} of {2}'.format(
|
|
|
|
name.capitalize(), counter + offset, total)
|
|
|
|
)
|
2016-02-13 09:14:30 -08:00
|
|
|
if name == 'tracks and reposts':
|
2016-04-10 07:50:45 -07:00
|
|
|
item_name = ''
|
2016-02-13 09:14:30 -08:00
|
|
|
if item['type'] == 'track-repost':
|
2016-04-10 07:50:45 -07:00
|
|
|
item_name = 'track'
|
2016-02-13 09:14:30 -08:00
|
|
|
else:
|
2016-04-10 07:50:45 -07:00
|
|
|
item_name = item['type']
|
|
|
|
uri = item[item_name]['uri']
|
2016-02-13 09:14:30 -08:00
|
|
|
parse_url(uri)
|
|
|
|
elif name == 'playlists':
|
|
|
|
download_playlist(item)
|
|
|
|
else:
|
|
|
|
download_track(item)
|
2015-05-09 15:51:22 -07:00
|
|
|
except Exception as e:
|
|
|
|
logger.exception(e)
|
2016-02-13 09:14:30 -08:00
|
|
|
logger.info('Downloaded all {0} {1} of user {2}!'.format(
|
|
|
|
total, name, username)
|
2016-02-08 05:32:59 -08:00
|
|
|
)
|
2014-11-16 09:19:42 -08:00
|
|
|
|
2014-10-12 15:16:18 -07:00
|
|
|
|
2014-10-23 07:14:29 -07:00
|
|
|
def download_playlist(playlist):
|
2014-11-16 09:19:42 -08:00
|
|
|
"""
|
|
|
|
Download a playlist
|
|
|
|
"""
|
2015-12-28 16:57:43 -08:00
|
|
|
global offset
|
2015-01-19 13:11:55 -08:00
|
|
|
invalid_chars = '\/:*?|<>"'
|
2016-04-23 08:25:19 -07:00
|
|
|
playlist_name = playlist['title'].encode('utf-8', 'ignore')
|
|
|
|
playlist_name = playlist_name.decode(sys.stdout.encoding)
|
2015-01-19 13:11:55 -08:00
|
|
|
playlist_name = ''.join(c for c in playlist_name if c not in invalid_chars)
|
|
|
|
|
|
|
|
if not os.path.exists(playlist_name):
|
|
|
|
os.makedirs(playlist_name)
|
|
|
|
os.chdir(playlist_name)
|
|
|
|
|
2016-01-31 05:29:57 -08:00
|
|
|
with open(playlist_name + '.m3u', 'w+') as playlist_file:
|
|
|
|
playlist_file.write('#EXTM3U' + os.linesep)
|
2016-02-08 05:01:13 -08:00
|
|
|
for counter, track_raw in enumerate(playlist['tracks'], 1):
|
2016-01-31 05:29:57 -08:00
|
|
|
if offset > 0:
|
|
|
|
offset -= 1
|
|
|
|
continue
|
2016-02-07 17:12:43 -08:00
|
|
|
logger.debug(track_raw)
|
2016-01-31 05:29:57 -08:00
|
|
|
logger.info('Track n°{0}'.format(counter))
|
2016-02-08 05:01:13 -08:00
|
|
|
download_track(track_raw, playlist['title'], playlist_file)
|
2015-01-19 13:11:55 -08:00
|
|
|
os.chdir('..')
|
2014-11-16 09:19:42 -08:00
|
|
|
|
2014-10-23 07:14:29 -07:00
|
|
|
|
2016-02-13 09:14:30 -08:00
|
|
|
def download_my_stream():
|
2015-01-05 14:22:14 -08:00
|
|
|
"""
|
2016-02-13 09:14:30 -08:00
|
|
|
DONT WORK FOR NOW
|
|
|
|
Download the stream of the current user
|
|
|
|
"""
|
2016-04-17 06:00:53 -07:00
|
|
|
# TODO
|
|
|
|
# Use Token
|
2016-02-13 09:14:30 -08:00
|
|
|
|
|
|
|
|
|
|
|
def download_all_of_a_page(tracks):
|
|
|
|
"""
|
|
|
|
NOT RECOMMENDED
|
2015-01-05 14:22:14 -08:00
|
|
|
Download all song of a page
|
|
|
|
"""
|
2016-02-08 05:32:59 -08:00
|
|
|
logger.error(
|
|
|
|
'NOTE: This will only download the songs of the page.(49 max)'
|
|
|
|
)
|
2016-01-10 04:49:09 -08:00
|
|
|
logger.error('I recommend you to provide a user link and a download type.')
|
2015-05-16 05:31:19 -07:00
|
|
|
for counter, track in enumerate(tracks, 1):
|
2015-05-09 04:01:49 -07:00
|
|
|
logger.newline()
|
2015-05-16 05:31:19 -07:00
|
|
|
logger.info('Track n°{0}'.format(counter))
|
2015-01-05 14:22:14 -08:00
|
|
|
download_track(track)
|
|
|
|
|
|
|
|
|
2015-08-26 14:21:15 -07:00
|
|
|
def download_track(track, playlist_name=None, playlist_file=None):
|
2014-11-16 09:19:42 -08:00
|
|
|
"""
|
|
|
|
Downloads a track
|
|
|
|
"""
|
2015-01-14 08:55:14 -08:00
|
|
|
global arguments
|
2014-11-16 09:19:42 -08:00
|
|
|
|
2016-03-01 14:58:59 -08:00
|
|
|
title = track['title']
|
|
|
|
title = title.encode('utf-8', 'ignore').decode(sys.stdout.encoding)
|
2016-02-07 16:04:16 -08:00
|
|
|
if track['streamable']:
|
2016-03-01 14:58:59 -08:00
|
|
|
url = '{0}?client_id={1}'.format(track['stream_url'], scdl_client_id)
|
2014-11-16 09:19:42 -08:00
|
|
|
else:
|
2016-02-07 17:35:51 -08:00
|
|
|
logger.error('{0} is not streamable...'.format(title))
|
2015-05-09 04:01:49 -07:00
|
|
|
logger.newline()
|
2014-11-16 09:19:42 -08:00
|
|
|
return
|
2015-05-09 15:13:11 -07:00
|
|
|
logger.info('Downloading {0}'.format(title))
|
2014-11-16 09:19:42 -08:00
|
|
|
|
2016-01-31 05:15:45 -08:00
|
|
|
# filename
|
2016-02-07 16:04:16 -08:00
|
|
|
if track['downloadable'] and not arguments['--onlymp3']:
|
2016-04-11 12:17:17 -07:00
|
|
|
logger.info('Downloading the original file.')
|
2016-02-07 16:04:16 -08:00
|
|
|
download_url = track['download_url']
|
|
|
|
url = '{0}?client_id={1}'.format(download_url, scdl_client_id)
|
2016-02-07 17:12:43 -08:00
|
|
|
r = requests.get(url, stream=True)
|
|
|
|
d = r.headers['content-disposition']
|
2016-02-29 08:25:59 -08:00
|
|
|
filename = re.findall("filename=(.+)", d)[0][1:-1]
|
2014-11-16 09:19:42 -08:00
|
|
|
else:
|
2015-01-05 02:05:41 -08:00
|
|
|
invalid_chars = '\/:*?|<>"'
|
2016-02-07 16:04:16 -08:00
|
|
|
username = track['user']['username']
|
|
|
|
if username not in title and arguments['--addtofile']:
|
|
|
|
title = '{0} - {1}'.format(username, title)
|
2014-11-16 09:19:42 -08:00
|
|
|
title = ''.join(c for c in title if c not in invalid_chars)
|
|
|
|
filename = title + '.mp3'
|
|
|
|
|
2016-02-07 17:35:51 -08:00
|
|
|
logger.debug("filename : {0}".format(filename))
|
2015-08-26 14:21:15 -07:00
|
|
|
# Add the track to the generated m3u playlist file
|
|
|
|
if playlist_file:
|
2016-02-07 16:04:16 -08:00
|
|
|
duration = math.floor(track['duration'] / 1000)
|
2016-02-08 05:32:59 -08:00
|
|
|
playlist_file.write(
|
|
|
|
'#EXTINF:{0},{1}{3}{2}{3}'.format(
|
|
|
|
duration, title, filename, os.linesep
|
|
|
|
)
|
|
|
|
)
|
2015-08-26 14:21:15 -07:00
|
|
|
|
2014-11-16 09:19:42 -08:00
|
|
|
# Download
|
|
|
|
if not os.path.isfile(filename):
|
2016-03-01 14:58:59 -08:00
|
|
|
logger.debug(url)
|
2016-02-07 17:12:43 -08:00
|
|
|
r = requests.get(url, stream=True)
|
2016-04-17 05:32:04 -07:00
|
|
|
if r.status_code == 401:
|
|
|
|
url = url[:-32] + alternative_client_id
|
|
|
|
logger.debug(url)
|
|
|
|
r = requests.get(url, stream=True)
|
2016-03-01 14:12:34 -08:00
|
|
|
temp = tempfile.NamedTemporaryFile(delete=False)
|
|
|
|
with temp as f:
|
2016-02-07 17:12:43 -08:00
|
|
|
total_length = int(r.headers.get('content-length'))
|
2016-02-08 05:32:59 -08:00
|
|
|
for chunk in progress.bar(
|
|
|
|
r.iter_content(chunk_size=1024),
|
2016-04-23 09:11:26 -07:00
|
|
|
expected_size=(total_length/1024) + 1,
|
|
|
|
hide=True if arguments["--hide-progress"] else False
|
2016-02-08 05:32:59 -08:00
|
|
|
):
|
2016-02-07 17:12:43 -08:00
|
|
|
if chunk:
|
|
|
|
f.write(chunk)
|
|
|
|
f.flush()
|
2016-03-01 14:12:34 -08:00
|
|
|
shutil.move(temp.name, os.path.join(os.getcwd(), filename))
|
2014-11-16 09:19:42 -08:00
|
|
|
if '.mp3' in filename:
|
|
|
|
try:
|
2016-02-13 09:14:30 -08:00
|
|
|
settags(track, filename, playlist_name)
|
2016-02-07 16:04:16 -08:00
|
|
|
except Exception as e:
|
2015-05-09 04:01:49 -07:00
|
|
|
logger.error('Error trying to set the tags...')
|
2016-02-07 16:04:16 -08:00
|
|
|
logger.debug(e)
|
2014-11-16 09:19:42 -08:00
|
|
|
else:
|
2015-05-09 15:13:11 -07:00
|
|
|
logger.error("This type of audio doesn't support tagging...")
|
2014-11-16 09:19:42 -08:00
|
|
|
else:
|
2015-05-09 15:13:11 -07:00
|
|
|
if arguments['-c']:
|
|
|
|
logger.info('{0} already Downloaded'.format(title))
|
2015-05-09 04:01:49 -07:00
|
|
|
logger.newline()
|
2014-11-16 09:19:42 -08:00
|
|
|
return
|
|
|
|
else:
|
2015-05-09 04:01:49 -07:00
|
|
|
logger.newline()
|
2015-05-09 15:13:11 -07:00
|
|
|
logger.error('Music already exists ! (exiting)')
|
2014-11-16 09:19:42 -08:00
|
|
|
sys.exit(0)
|
|
|
|
|
2015-05-09 04:01:49 -07:00
|
|
|
logger.newline()
|
2015-05-09 15:13:11 -07:00
|
|
|
logger.info('{0} Downloaded.'.format(filename))
|
2015-05-09 04:01:49 -07:00
|
|
|
logger.newline()
|
2014-11-16 09:19:42 -08:00
|
|
|
|
2014-10-20 13:23:46 -07:00
|
|
|
|
2015-12-21 10:51:23 -08:00
|
|
|
def settags(track, filename, album=None):
|
2014-11-16 09:19:42 -08:00
|
|
|
"""
|
|
|
|
Set the tags to the mp3
|
|
|
|
"""
|
2015-05-09 15:13:11 -07:00
|
|
|
logger.info('Settings tags...')
|
2016-02-07 16:04:16 -08:00
|
|
|
artwork_url = track['artwork_url']
|
2016-04-17 06:00:53 -07:00
|
|
|
user = track['user']
|
2016-02-08 05:32:59 -08:00
|
|
|
if not artwork_url:
|
2016-04-17 06:00:53 -07:00
|
|
|
artwork_url = user['avatar_url']
|
2014-11-16 09:19:42 -08:00
|
|
|
artwork_url = artwork_url.replace('large', 't500x500')
|
2016-02-07 17:12:43 -08:00
|
|
|
response = requests.get(artwork_url, stream=True)
|
2016-03-01 14:12:34 -08:00
|
|
|
with tempfile.NamedTemporaryFile() as out_file:
|
2016-02-07 17:12:43 -08:00
|
|
|
shutil.copyfileobj(response.raw, out_file)
|
2016-03-01 14:12:34 -08:00
|
|
|
out_file.seek(0)
|
|
|
|
|
|
|
|
audio = mutagen.File(filename)
|
|
|
|
audio['TIT2'] = mutagen.id3.TIT2(encoding=3, text=track['title'])
|
2016-04-17 06:00:53 -07:00
|
|
|
audio['TPE1'] = mutagen.id3.TPE1(encoding=3, text=user['username'])
|
2016-03-01 14:12:34 -08:00
|
|
|
audio['TCON'] = mutagen.id3.TCON(encoding=3, text=track['genre'])
|
|
|
|
if album:
|
|
|
|
audio['TALB'] = mutagen.id3.TALB(encoding=3, text=album)
|
|
|
|
if artwork_url:
|
|
|
|
audio['APIC'] = mutagen.id3.APIC(
|
|
|
|
encoding=3, mime='image/jpeg', type=3, desc='Cover',
|
|
|
|
data=out_file.read()
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
logger.error('Artwork can not be set.')
|
2014-11-16 09:19:42 -08:00
|
|
|
audio.save()
|
|
|
|
|
2014-10-23 07:14:29 -07:00
|
|
|
|
2014-10-20 13:23:46 -07:00
|
|
|
def signal_handler(signal, frame):
|
2014-11-16 09:19:42 -08:00
|
|
|
"""
|
2016-02-13 09:14:30 -08:00
|
|
|
Handle Keyboardinterrupt
|
2014-11-16 09:19:42 -08:00
|
|
|
"""
|
2015-05-09 04:01:49 -07:00
|
|
|
logger.newline()
|
|
|
|
logger.info('Good bye!')
|
2014-11-16 09:19:42 -08:00
|
|
|
sys.exit(0)
|
2014-10-12 15:16:18 -07:00
|
|
|
|
2015-05-09 15:13:11 -07:00
|
|
|
if __name__ == '__main__':
|
2014-11-16 09:19:42 -08:00
|
|
|
main()
|