commit
6da200ef97
228
scdl/scdl.py
228
scdl/scdl.py
|
@ -32,30 +32,26 @@ Options:
|
|||
--debug Print every information and
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||
|
||||
from docopt import docopt
|
||||
import configparser
|
||||
|
||||
import json
|
||||
import logging
|
||||
import warnings
|
||||
import os
|
||||
import signal
|
||||
import sys
|
||||
import time
|
||||
|
||||
import soundcloud
|
||||
import wget
|
||||
import urllib.request
|
||||
import json
|
||||
import warnings
|
||||
|
||||
import configparser
|
||||
import mutagen
|
||||
import wget
|
||||
from docopt import docopt
|
||||
from requests.exceptions import HTTPError
|
||||
|
||||
import mutagen
|
||||
|
||||
from scdl import __version__
|
||||
from scdl import utils
|
||||
from scdl import soundcloud, utils
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format='%(asctime)-15s %(name)-5s %(levelname)-8s %(message)s')
|
||||
logging.getLogger('urllib3.connectionpool').setLevel(logging.ERROR)
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.setLevel(logging.INFO)
|
||||
logger.addFilter(utils.ColorizeFilter())
|
||||
|
@ -84,44 +80,44 @@ def main():
|
|||
# Parse argument
|
||||
arguments = docopt(__doc__, version=__version__)
|
||||
|
||||
if arguments["--debug"]:
|
||||
if arguments['--debug']:
|
||||
logger.level = logging.DEBUG
|
||||
elif arguments["--error"]:
|
||||
elif arguments['--error']:
|
||||
logger.level = logging.ERROR
|
||||
|
||||
logger.info("Soundcloud Downloader")
|
||||
logger.info('Soundcloud Downloader')
|
||||
logger.debug(arguments)
|
||||
|
||||
if arguments["-o"] is not None:
|
||||
if arguments['-o'] is not None:
|
||||
try:
|
||||
offset = int(arguments["-o"])
|
||||
offset = int(arguments['-o'])
|
||||
except:
|
||||
logger.error('Offset should be an Integer...')
|
||||
sys.exit()
|
||||
|
||||
if arguments["--hidewarnings"]:
|
||||
warnings.filterwarnings("ignore")
|
||||
if arguments['--hidewarnings']:
|
||||
warnings.filterwarnings('ignore')
|
||||
|
||||
if arguments["--path"] is not None:
|
||||
if os.path.exists(arguments["--path"]):
|
||||
os.chdir(arguments["--path"])
|
||||
if arguments['--path'] is not None:
|
||||
if os.path.exists(arguments['--path']):
|
||||
os.chdir(arguments['--path'])
|
||||
else:
|
||||
logger.error('Invalid path in arguments...')
|
||||
sys.exit()
|
||||
logger.debug('Downloading to '+os.getcwd()+'...')
|
||||
|
||||
logger.newline()
|
||||
if arguments["-l"]:
|
||||
parse_url(arguments["-l"])
|
||||
elif arguments["me"]:
|
||||
if arguments["-a"]:
|
||||
if arguments['-l']:
|
||||
parse_url(arguments['-l'])
|
||||
elif arguments['me']:
|
||||
if arguments['-a']:
|
||||
download_all_user_tracks(who_am_i())
|
||||
elif arguments["-f"]:
|
||||
download_user_favorites(who_am_i())
|
||||
elif arguments["-t"]:
|
||||
download_user_tracks(who_am_i())
|
||||
elif arguments["-p"]:
|
||||
download_user_playlists(who_am_i())
|
||||
elif arguments['-f']:
|
||||
download_all_of_user(who_am_i(), 'favorite', download_track)
|
||||
elif arguments['-t']:
|
||||
download_all_of_user(who_am_i(), 'track', download_track)
|
||||
elif arguments['-p']:
|
||||
download_all_of_user(who_am_i(), 'playlist', download_playlist)
|
||||
|
||||
|
||||
def get_config():
|
||||
|
@ -157,7 +153,7 @@ def get_item(track_url):
|
|||
try:
|
||||
item = client.get('/resolve', url=track_url)
|
||||
except Exception as e:
|
||||
logger.error("Could not resolve url " + track_url)
|
||||
logger.error('Could not resolve url {0}'.format(track_url))
|
||||
logger.exception(e)
|
||||
sys.exit(0)
|
||||
return item
|
||||
|
@ -175,25 +171,25 @@ def parse_url(track_url):
|
|||
elif isinstance(item, soundcloud.resource.ResourceList):
|
||||
download_all(item)
|
||||
elif item.kind == 'track':
|
||||
logger.info("Found a track")
|
||||
logger.info('Found a track')
|
||||
download_track(item)
|
||||
elif item.kind == "playlist":
|
||||
logger.info("Found a playlist")
|
||||
elif item.kind == 'playlist':
|
||||
logger.info('Found a playlist')
|
||||
download_playlist(item)
|
||||
elif item.kind == 'user':
|
||||
logger.info("Found an user profile")
|
||||
if arguments["-f"]:
|
||||
download_user_favorites(item)
|
||||
elif arguments["-t"]:
|
||||
download_user_tracks(item)
|
||||
elif arguments["-a"]:
|
||||
logger.info('Found an user profile')
|
||||
if arguments['-f']:
|
||||
download_all_of_user(item, 'favorite', download_track)
|
||||
elif arguments['-t']:
|
||||
download_all_of_user(item, 'track', download_track)
|
||||
elif arguments['-a']:
|
||||
download_all_user_tracks(item)
|
||||
elif arguments["-p"]:
|
||||
download_user_playlists(item)
|
||||
elif arguments['-p']:
|
||||
download_all_of_user(item, 'playlist', download_playlist)
|
||||
else:
|
||||
logger.error('Please provide a download type...')
|
||||
else:
|
||||
logger.error("Unknown item type")
|
||||
logger.error('Unknown item type')
|
||||
|
||||
|
||||
def who_am_i():
|
||||
|
@ -208,7 +204,7 @@ def who_am_i():
|
|||
except:
|
||||
logger.error('Invalid token...')
|
||||
sys.exit(0)
|
||||
logger.info('Hello ' + current_user.username + '!')
|
||||
logger.info('Hello {0.username}!'.format(current_user))
|
||||
logger.newline()
|
||||
return current_user
|
||||
|
||||
|
@ -218,81 +214,44 @@ def download_all_user_tracks(user):
|
|||
Find track & repost of the user
|
||||
"""
|
||||
global offset
|
||||
user_id = user.id
|
||||
|
||||
url = "https://api.sndcdn.com/e1/users/%s/sounds.json?limit=1&offset=%d&client_id=%s" % (user_id, offset, scdl_client_id)
|
||||
url = 'https://api.sndcdn.com/e1/users/{0.id}/sounds.json?limit=1&offset={1}&client_id={2}'.format(user, offset, scdl_client_id)
|
||||
response = urllib.request.urlopen(url)
|
||||
data = response.read()
|
||||
text = data.decode('utf-8')
|
||||
json_data = json.loads(text)
|
||||
while str(json_data) != '[]':
|
||||
while json_data:
|
||||
offset += 1
|
||||
try:
|
||||
this_url = json_data[0]['track']['uri']
|
||||
except:
|
||||
this_url = json_data[0]['playlist']['uri']
|
||||
logger.info('Track n°%d' % (offset))
|
||||
logger.info('Track n°{0}'.format(offset))
|
||||
parse_url(this_url)
|
||||
|
||||
url = "https://api.sndcdn.com/e1/users/%s/sounds.json?limit=1&offset=%d&client_id=%s" % (user_id, offset, scdl_client_id)
|
||||
url = 'https://api.sndcdn.com/e1/users/{0.id}/sounds.json?limit=1&offset={1}&client_id={2}'.format(user, offset, scdl_client_id)
|
||||
response = urllib.request.urlopen(url)
|
||||
data = response.read()
|
||||
text = data.decode('utf-8')
|
||||
json_data = json.loads(text)
|
||||
|
||||
|
||||
def download_user_tracks(user):
|
||||
def download_all_of_user(user, name, download_function):
|
||||
"""
|
||||
Find track in user upload --> no repost
|
||||
Download all items of an user. Can be playlist or track, or whatever handled by the download function.
|
||||
"""
|
||||
global offset
|
||||
count = 0
|
||||
tracks = client.get('/users/' + str(user.id) + '/tracks', limit=10, offset=offset)
|
||||
for track in tracks:
|
||||
for track in tracks:
|
||||
count += 1
|
||||
logger.newline()
|
||||
logger.info('Track n°%d' % (count))
|
||||
download_track(track)
|
||||
offset += 10
|
||||
tracks = client.get('/users/' + str(user.id) + '/tracks', limit=10, offset=offset)
|
||||
logger.info('All users track downloaded!')
|
||||
|
||||
|
||||
def download_user_playlists(user):
|
||||
"""
|
||||
Find playlists of the user
|
||||
"""
|
||||
global offset
|
||||
count = 0
|
||||
playlists = client.get('/users/' + str(user.id) + '/playlists', limit=10, offset=offset)
|
||||
for playlist in playlists:
|
||||
for playlist in playlists:
|
||||
count += 1
|
||||
logger.newline()
|
||||
logger.info('Playlist n°%d' % (count))
|
||||
download_playlist(playlist)
|
||||
offset += 10
|
||||
playlists = client.get('/users/' + str(user.id) + '/playlists', limit=10, offset=offset)
|
||||
logger.info('All users playlists downloaded!')
|
||||
|
||||
|
||||
def download_user_favorites(user):
|
||||
"""
|
||||
Find tracks in user favorites
|
||||
"""
|
||||
global offset
|
||||
count = 0
|
||||
favorites = client.get('/users/' + str(user.id) + '/favorites', limit=10, offset=offset)
|
||||
for track in favorites:
|
||||
for track in favorites:
|
||||
count += 1
|
||||
logger.newline()
|
||||
logger.info('Favorite n°%d' % (count))
|
||||
download_track(track)
|
||||
offset += 10
|
||||
favorites = client.get('/users/' + str(user.id) + '/favorites', limit=10, offset=offset)
|
||||
logger.info('All users favorites downloaded!')
|
||||
logger.info('Retrieving the {1}s of user {0.username}...'.format(user, name))
|
||||
items = client.get_all('/users/{0.id}/{1}s'.format(user, name))
|
||||
total = len(items)
|
||||
s = '' if total == 1 else 's'
|
||||
logger.info('Retrieved {2} {0}{1}'.format(name, s, total))
|
||||
for counter, item in enumerate(items, 1):
|
||||
try:
|
||||
logger.info('{0} n°{1} of {2}'.format(name.capitalize(), counter, total))
|
||||
download_function(item)
|
||||
except Exception as e:
|
||||
logger.exception(e)
|
||||
logger.info('Downloaded all {2} {0}{1} of user {3.username}!'.format(name, s, total, user))
|
||||
|
||||
|
||||
def download_my_stream():
|
||||
|
@ -309,9 +268,7 @@ def download_playlist(playlist):
|
|||
"""
|
||||
Download a playlist
|
||||
"""
|
||||
count = 0
|
||||
invalid_chars = '\/:*?|<>"'
|
||||
|
||||
playlist_name = playlist.title.encode('utf-8', 'ignore').decode('utf-8')
|
||||
playlist_name = ''.join(c for c in playlist_name if c not in invalid_chars)
|
||||
|
||||
|
@ -319,10 +276,9 @@ def download_playlist(playlist):
|
|||
os.makedirs(playlist_name)
|
||||
os.chdir(playlist_name)
|
||||
|
||||
for track_raw in playlist.tracks:
|
||||
count += 1
|
||||
mp3_url = get_item(track_raw["permalink_url"])
|
||||
logger.info('Track n°%d' % (count))
|
||||
for counter, track_raw in enumerate(playlist.tracks, 1):
|
||||
mp3_url = get_item(track_raw['permalink_url'])
|
||||
logger.info('Track n°{0}'.format(counter))
|
||||
download_track(mp3_url, playlist.title)
|
||||
|
||||
os.chdir('..')
|
||||
|
@ -333,20 +289,17 @@ def download_all(tracks):
|
|||
Download all song of a page
|
||||
Not recommended
|
||||
"""
|
||||
logger.error("NOTE: This will only download the songs of the page.(49 max)")
|
||||
logger.error("I recommend you to provide an user link and a download type.")
|
||||
count = 0
|
||||
for track in tracks:
|
||||
count += 1
|
||||
logger.error('NOTE: This will only download the songs of the page.(49 max)')
|
||||
logger.error('I recommend you to provide an user link and a download type.')
|
||||
for counter, track in enumerate(tracks, 1):
|
||||
logger.newline()
|
||||
logger.info('Track n°%d' % (count))
|
||||
logger.info('Track n°{0}'.format(counter))
|
||||
download_track(track)
|
||||
|
||||
|
||||
def alternative_download(track):
|
||||
logger.debug('alternative_download used')
|
||||
track_id = str(track.id)
|
||||
url = 'http://api.soundcloud.com/i1/tracks/' + track_id + '/streams?client_id=a3e059563d7fd3372b49b37f00a00bcf'
|
||||
url = 'http://api.soundcloud.com/i1/tracks/{0.id}/streams?client_id=a3e059563d7fd3372b49b37f00a00bcf'.format(track)
|
||||
res = urllib.request.urlopen(url)
|
||||
data = res.read().decode('utf-8')
|
||||
json_data = json.loads(data)
|
||||
|
@ -371,25 +324,25 @@ def download_track(track, playlist_name=None):
|
|||
except HTTPError:
|
||||
url = alternative_download(track)
|
||||
else:
|
||||
logger.error('%s is not streamable...' % (track.title))
|
||||
logger.error('{0.title} is not streamable...'.format(track))
|
||||
logger.newline()
|
||||
return
|
||||
title = track.title
|
||||
title = title.encode('utf-8', 'ignore').decode(sys.stdout.encoding)
|
||||
logger.info("Downloading " + title)
|
||||
logger.info('Downloading {0}'.format(title))
|
||||
|
||||
#filename
|
||||
if track.downloadable and not arguments["--onlymp3"]:
|
||||
if track.downloadable and not arguments['--onlymp3']:
|
||||
logger.info('Downloading the orginal file.')
|
||||
url = track.download_url + '?client_id=' + scdl_client_id
|
||||
url = '{0.download_url}?client_id={1}'.format(track, scdl_client_id)
|
||||
|
||||
filename = urllib.request.urlopen(url).info()['Content-Disposition'].split('filename=')[1]
|
||||
if filename[0] == '"' or filename[0] == "'":
|
||||
filename = filename[1:-1]
|
||||
else:
|
||||
invalid_chars = '\/:*?|<>"'
|
||||
if track.user['username'] not in title and arguments["--addtofile"]:
|
||||
title = track.user['username'] + ' - ' + title
|
||||
if track.user['username'] not in title and arguments['--addtofile']:
|
||||
title = '{0.user[username]} - {1}'.format(track, title)
|
||||
title = ''.join(c for c in title if c not in invalid_chars)
|
||||
filename = title + '.mp3'
|
||||
|
||||
|
@ -406,19 +359,19 @@ def download_track(track, playlist_name=None):
|
|||
except:
|
||||
logger.error('Error trying to set the tags...')
|
||||
else:
|
||||
logger.error('This type of audio doesn\'t support tagging...')
|
||||
logger.error("This type of audio doesn't support tagging...")
|
||||
else:
|
||||
if arguments["-c"]:
|
||||
logger.info(title + " already Downloaded")
|
||||
if arguments['-c']:
|
||||
logger.info('{0} already Downloaded'.format(title))
|
||||
logger.newline()
|
||||
return
|
||||
else:
|
||||
logger.newline()
|
||||
logger.error("Music already exists ! (exiting)")
|
||||
logger.error('Music already exists ! (exiting)')
|
||||
sys.exit(0)
|
||||
|
||||
logger.newline()
|
||||
logger.info(filename + ' Downloaded.')
|
||||
logger.info('{0} Downloaded.'.format(filename))
|
||||
logger.newline()
|
||||
|
||||
|
||||
|
@ -426,8 +379,8 @@ def settags(track, filename, album='Soundcloud'):
|
|||
"""
|
||||
Set the tags to the mp3
|
||||
"""
|
||||
logger.info("Settings tags...")
|
||||
user = client.get('/users/' + str(track.user_id), allow_redirects=False)
|
||||
logger.info('Settings tags...')
|
||||
user = client.get('/users/{0.user_id}'.format(track), allow_redirects=False)
|
||||
|
||||
artwork_url = track.artwork_url
|
||||
if artwork_url is None:
|
||||
|
@ -436,14 +389,15 @@ def settags(track, filename, album='Soundcloud'):
|
|||
urllib.request.urlretrieve(artwork_url, '/tmp/scdl.jpg')
|
||||
|
||||
audio = mutagen.File(filename)
|
||||
audio["TIT2"] = mutagen.id3.TIT2(encoding=3, text=track.title)
|
||||
audio["TALB"] = mutagen.id3.TALB(encoding=3, text=album)
|
||||
audio["TPE1"] = mutagen.id3.TPE1(encoding=3, text=user.username)
|
||||
audio["TCON"] = mutagen.id3.TCON(encoding=3, text=track.genre)
|
||||
audio['TIT2'] = mutagen.id3.TIT2(encoding=3, text=track.title)
|
||||
audio['TALB'] = mutagen.id3.TALB(encoding=3, text=album)
|
||||
audio['TPE1'] = mutagen.id3.TPE1(encoding=3, text=user.username)
|
||||
audio['TCON'] = mutagen.id3.TCON(encoding=3, text=track.genre)
|
||||
if artwork_url is not None:
|
||||
audio["APIC"] = mutagen.id3.APIC(encoding=3, mime='image/jpeg', type=3, desc='Cover', data=open('/tmp/scdl.jpg', 'rb').read())
|
||||
audio['APIC'] = mutagen.id3.APIC(encoding=3, mime='image/jpeg', type=3, desc='Cover',
|
||||
data=open('/tmp/scdl.jpg', 'rb').read())
|
||||
else:
|
||||
logger.error("Artwork can not be set.")
|
||||
logger.error('Artwork can not be set.')
|
||||
audio.save()
|
||||
|
||||
|
||||
|
@ -454,12 +408,12 @@ def signal_handler(signal, frame):
|
|||
time.sleep(1)
|
||||
files = os.listdir()
|
||||
for f in files:
|
||||
if not os.path.isdir(f) and ".tmp" in f:
|
||||
if not os.path.isdir(f) and '.tmp' in f:
|
||||
os.remove(f)
|
||||
|
||||
logger.newline()
|
||||
logger.info('Good bye!')
|
||||
sys.exit(0)
|
||||
|
||||
if __name__ == "__main__":
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
# -*- encoding: utf-8 -*-
|
||||
|
||||
import soundcloud
|
||||
|
||||
__all__ = ('Client', 'resource')
|
||||
|
||||
|
||||
class Client(soundcloud.Client):
|
||||
|
||||
def get_all(self, url, offset=0, limit=200, **kwargs):
|
||||
resources = set()
|
||||
prev_offset, start_offset = None, offset
|
||||
while offset != prev_offset:
|
||||
resources.update(self.get(url, offset=offset, limit=limit, **kwargs))
|
||||
prev_offset, offset = offset, start_offset + len(resources)
|
||||
return resources
|
||||
|
||||
resource = soundcloud.resource
|
|
@ -4,8 +4,6 @@
|
|||
Copied from https://github.com/davidfischer-ch/pytoolbox/blob/master/pytoolbox/logging.py
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import, division, print_function, unicode_literals
|
||||
|
||||
import logging
|
||||
from termcolor import colored
|
||||
|
||||
|
|
Loading…
Reference in New Issue