Merge pull request #63 from davidfischer-ch/dev

Pull request for #59 #60 #61
master
Ronan Nello 2015-06-28 21:01:30 +02:00
commit 6da200ef97
3 changed files with 109 additions and 139 deletions

View File

@ -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}{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()

18
scdl/soundcloud.py Normal file
View File

@ -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

View File

@ -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