Created
February 24, 2017 18:58
-
-
Save Hopfengetraenk/457e5948403c127b67e76af150e1526d to your computer and use it in GitHub Desktop.
DeezerDownload
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!python2 | |
#coding:latin | |
""" | |
Author: --<> | |
Purpose: | |
Download and decrypt songs from deezer. | |
The song is saved as a mp3. | |
No ID3 tags are added to the file. | |
The filename contains album, artist, song title. | |
Usage: | |
python DeezerDownload.py http://www.deezer.com/album/6671241/ | |
python DeezerDownload.py | |
This will create mp3's in the '\downloads' directory, with the song information in the filenames. | |
Created: 16.02.2017 | |
""" | |
config_DL_Dir = 'downloads' | |
config_topsongs_limit = 50 | |
import sys | |
from Crypto.Hash import MD5 | |
from Crypto.Cipher import AES, Blowfish | |
import re | |
import os | |
import json | |
import struct | |
import urllib | |
import urllib2 | |
import HTMLParser | |
import copy | |
import traceback | |
import csv | |
import threading | |
import time | |
import httplib | |
import requests | |
from mutagen.mp3 import MP3 | |
from mutagen.id3 import ID3, APIC, error | |
from decimal import Decimal | |
from colorama import Fore, Back, Style, init | |
from tkFileDialog import askopenfilename as openfile | |
import pyglet | |
import feedparser | |
import unicodedata | |
from binascii import a2b_hex, b2a_hex | |
import string | |
#load AVBin | |
try: | |
pyglet.lib.load_library('avbin') | |
except Exception as e: | |
print 'Trying to load avbin64...' | |
pyglet.lib.load_library('avbin64') | |
print 'Success!' | |
pyglet.have_avbin=True | |
# global variable | |
host_stream_cdn = "http://e-cdn-proxy-%s.deezer.com/mobile/1" | |
setting_domain_img = "http://cdn-images.deezer.com/images" | |
httplib.HTTPConnection._http_vsn = 10 | |
httplib.HTTPConnection._http_vsn_str = 'HTTP/1.0' | |
def enabletor(): | |
import socks | |
import socket | |
def create_connection(address, timeout=None, source_address=None): | |
sock = socks.socksocket() | |
sock.connect(address) | |
return sock | |
socks.setdefaultproxy(socks.PROXY_TYPE_SOCKS5, "127.0.0.1", 9050) | |
# patch the socket module | |
socket.socket = socks.socksocket | |
socket.create_connection = create_connection | |
# todo: test if tor connection really works by connecting to https://check.torproject.org/ | |
class ScriptExtractor(HTMLParser.HTMLParser): | |
""" extract <script> tag contents from a html page """ | |
def __init__(self): | |
HTMLParser.HTMLParser.__init__(self) | |
self.scripts = [] | |
self.curtag = None | |
def handle_starttag(self, tag, attrs): | |
self.curtag = tag.lower() | |
def handle_data(self, data): | |
if self.curtag == "script": | |
self.scripts.append(data) | |
def handle_endtag(self, tag): | |
self.curtag = None | |
def find_re(txt, regex): | |
""" Return either the first regex group, or the entire match """ | |
m = re.search(regex, txt) | |
if not m: | |
return | |
gr = m.groups() | |
if gr: | |
return gr[0] | |
return m.group() | |
def find_songs(obj): | |
""" recurse into json object, yielding all objects which look like song descriptions """ | |
if type( obj ) == list: | |
for item in obj: | |
for tItem in find_songs(item): | |
yield tItem | |
#yield from find_songs(item) | |
elif type( obj ) == dict: | |
if "SNG_ID" in obj: | |
yield obj | |
for v in obj.values(): | |
for tItem in find_songs(v): | |
yield tItem | |
#yield from find_songs(v) | |
def parse_deezer_page(url): | |
""" | |
extracts download host, and yields any songs found in a page. | |
""" | |
f = urllib2.urlopen(url) | |
data = f.read() | |
parser = ScriptExtractor() | |
parser.feed(data.decode('utf-8')) | |
parser.close() | |
# note: keeping track of songs by songid, this might still lead to duplicate downloads, | |
# for instance when the same song is present on multiple albums. | |
# if you want to avoid that, add the MD5_ORIGIN to the foundsongs set, instead of the SNG_ID. | |
foundsongs = set() | |
for script in parser.scripts: | |
# http://e-cdn-proxy-%s.deezer.com/mobile/1/ | |
var = find_re(script, r'var HOST_STREAM_CDN\s*=\s*\'(.*?)\';') | |
if var: | |
global host_stream_cdn | |
host_stream_cdn = var.replace("{0}", "%s") | |
# http://cdn-images.deezer.com/images | |
var = find_re(script, r'var SETTING_DOMAIN_IMG\s*=\s*\'(.*?)\';') | |
if var: | |
global setting_domain_img | |
setting_domain_img = var | |
#.*MD5_ORIGIN -> PLAYER_INIT & __DZR_APP_STATE__ | |
#.*LABEL_NAME -> __DZR_APP_STATE__ | |
jsondata = find_re(script, r'\{.*LABEL_NAME.*\}') | |
if jsondata: | |
DZR_APP_STATE = json.loads( jsondata ) | |
global album_Data | |
album_Data = DZR_APP_STATE.get("DATA") | |
# Add num of tracks | |
try: | |
album_Data["TRACKS"] = DZR_APP_STATE["SONGS"]["total"] | |
except: | |
pass | |
for song in find_songs( DZR_APP_STATE.get("SONGS") ): | |
if song["SNG_ID"] not in foundsongs: | |
yield song | |
foundsongs.add(song["SNG_ID"]) | |
def md5hex(data): | |
""" return hex string of md5 of the given string """ | |
h = MD5.new() | |
h.update( data) | |
return b2a_hex( h.digest() ) | |
def hexaescrypt(data, key): | |
""" returns hex string of aes encrypted data """ | |
c = AES.new( key, AES.MODE_ECB) | |
return b2a_hex( c.encrypt(data) ) | |
def genurlkey( songid, md5origin, mediaver=4, fmt=1): | |
""" Calculate the deezer download url given the songid, origin and media+format """ | |
data = '\xa4'.join(_.encode("utf-8") for _ in [ | |
md5origin, | |
str( fmt ), | |
str( songid ), | |
str( mediaver ) | |
]) | |
data = '\xa4'.join( [ md5hex(data), data ] ) + '\xa4' | |
if len(data)%16: | |
data += b'\0' * (16-len(data)%16) | |
return hexaescrypt(data, "jo6aey6haid2Teih" ).decode('utf-8') | |
def calcbfkey(songid): | |
""" Calculate the Blowfish decrypt key for a given songid """ | |
h = md5hex( "%d" % songid) | |
key = "g4el58wc0zvf9na1" | |
return "".join( | |
chr( | |
ord( h[ i ] ) ^ | |
ord( h[ i + 16] ) ^ | |
ord( key[i] ) | |
) for i in range( 16 ) | |
) | |
def blowfishDecrypt(data, key): | |
""" CBC decrypt data with key """ | |
c = Blowfish.new( key , | |
Blowfish.MODE_CBC, | |
a2b_hex( "0001020304050607" ) | |
) | |
return c.decrypt(data) | |
def decryptfile(fh, key, fo): | |
""" | |
Decrypt data from file <fh>, and write to file <fo>. | |
decrypt using blowfish with <key>. | |
Only every third 2048 byte block is encrypted. | |
""" | |
blockSize = 0x800 #2048 byte | |
i = 0 | |
while True: | |
data = fh.read( blockSize ) | |
if not data: | |
break | |
isEncrypted = ( (i % 3) == 0 ) | |
isWholeBlock = len(data) == blockSize | |
if isEncrypted and isWholeBlock: | |
data = blowfishDecrypt(data, key) | |
fo.write(data) | |
i += 1 | |
## Built-in namespace | |
#import __builtin__ | |
## Extended dict | |
#class myDict(dict): | |
#def s(self, key): | |
#""" return value as UTF-8 String""" | |
#if self: | |
#return self.get(key).encode('utf-8') | |
#else: | |
#return '' | |
## Substitute the original str with the subclass on the built-in namespace | |
#__builtin__.dict = myDict | |
##type bugfix | |
#dict = type( {} ) | |
def getformat(song): | |
""" return format id for a song """ | |
return 3 if song.get("FILESIZE_MP3_320") else \ | |
5 if song.get("FILESIZE_MP3_256") else \ | |
1 | |
#FILESIZE_MP3_128 FILESIZE_MP3_64 FILESIZE_AAC_64 | |
def writeid3v1_1(fo, song): | |
# Bugfix changed song["SNG_TITLE... to song.get("SNG_TITLE... to avoid 'key-error' in case the key does not exist | |
def song_get(song, key): | |
try: | |
return song.get(key).encode('utf-8') | |
except Exception as e: | |
return "" | |
def album_get(key): | |
global album_Data | |
try: | |
return album_Data.get(key).encode('utf-8') | |
except Exception as e: | |
return "" | |
data = struct.pack("3s" "30s" "30s" "30s" "4s" "28sB" "B" "B", | |
"TAG", # header | |
song_get (song, "SNG_TITLE"), # title | |
song_get (song, "ART_NAME") , # artist | |
song_get (song, "ALB_TITLE"), # album | |
album_get("PHYSICAL_RELEASE_DATE"), # year | |
album_get("LABEL_NAME"), 0, # comment | |
int(song_get(song, "TRACK_NUMBER") or 0), # tracknum | |
255 # genre | |
) | |
fo.write( data ) | |
def downloadpicture(id): | |
try: | |
fh = urllib2.urlopen( | |
setting_domain_img + "/cover/" + id + "/1200x1200.jpg" | |
) | |
return fh.read() | |
except Exception as e: | |
print "no pic", e | |
def writeid3v2(fo, song): | |
def make28bit(x): | |
return ( | |
(x<<3) & 0x7F000000) | ( | |
(x<<2) & 0x7F0000) | ( | |
(x<<1) & 0x7F00) | \ | |
(x & 0x7F) | |
def maketag(tag, content): | |
return struct.pack( ">4sLH", | |
tag.encode( "ascii" ), | |
len(content), | |
0 | |
) + content | |
def album_get(key): | |
global album_Data | |
try: | |
return album_Data.get(key)#.encode('utf-8') | |
except Exception as e: | |
return "" | |
def song_get(song, key): | |
try: | |
return song[key]#.encode('utf-8') | |
except Exception as e: | |
return "" | |
def makeutf8(txt): | |
return b"\x03" + txt .encode('utf-8') | |
def makepic(data): | |
# Picture type: | |
# 0x00 Other | |
# 0x01 32x32 pixels 'file icon' (PNG only) | |
# 0x02 Other file icon | |
# 0x03 Cover (front) | |
# 0x04 Cover (back) | |
# 0x05 Leaflet page | |
# 0x06 Media (e.g. lable side of CD) | |
# 0x07 Lead artist/lead performer/soloist | |
# 0x08 Artist/performer | |
# 0x09 Conductor | |
# 0x0A Band/Orchestra | |
# 0x0B Composer | |
# 0x0C Lyricist/text writer | |
# 0x0D Recording Location | |
# 0x0E During recording | |
# 0x0F During performance | |
# 0x10 Movie/video screen capture | |
# 0x11 A bright coloured fish | |
# 0x12 Illustration | |
# 0x13 Band/artist logotype | |
# 0x14 Publisher/Studio logotype | |
imgframe = ( "\x00", # text encoding | |
"image/jpeg", "\0", # mime type | |
"\x03", # picture type: 'Cover (front)' | |
""[:64], "\0", # description | |
data | |
) | |
return b'' .join( imgframe ) | |
# get Data as DDMM | |
try: | |
phyDate_YYYYMMDD = album_get("PHYSICAL_RELEASE_DATE") .split('-') #'2008-11-21' | |
phyDate_DDMM = phyDate_YYYYMMDD[2] + phyDate_YYYYMMDD[1] | |
except: | |
phyDate_DDMM = '' | |
# get size of first item in the list that is not 0 | |
try: | |
FileSize = [ | |
song_get(song,i) | |
for i in ( | |
'FILESIZE_AAC_64', | |
'FILESIZE_MP3_320', | |
'FILESIZE_MP3_256', | |
'FILESIZE_MP3_64', | |
'FILESIZE', | |
) if song_get(song,i) | |
][0] | |
except: | |
FileSize = 0 | |
try: | |
track = "%02s" % song["TRACK_NUMBER"] | |
track += "/%02s" % album_get("TRACKS") | |
except: | |
pass | |
# http://id3.org/id3v2.3.0#Attached_picture | |
id3 = [ | |
maketag( "TRCK", makeutf8( track ) ), # The 'Track number/Position in set' frame is a numeric string containing the order number of the audio-file on its original recording. This may be extended with a "/" character and a numeric string containing the total numer of tracks/elements on the original recording. E.g. "4/9". | |
maketag( "TLEN", makeutf8( str( int(song["DURATION"]) * 1000 ) ) ), # The 'Length' frame contains the length of the audiofile in milliseconds, represented as a numeric string. | |
maketag( "TORY", makeutf8( str( album_get("PHYSICAL_RELEASE_DATE")[:4] )) ), # The 'Original release year' frame is intended for the year when the original recording was released. if for example the music in the file should be a cover of a previously released song | |
maketag( "TYER", makeutf8( str( album_get("DIGITAL_RELEASE_DATE" )[:4] )) ), # The 'Year' frame is a numeric string with a year of the recording. This frames is always four characters long (until the year 10000). | |
maketag( "TDAT", makeutf8( str( phyDate_DDMM )) ), # The 'Date' frame is a numeric string in the DDMM format containing the date for the recording. This field is always four characters long. | |
maketag( "TPUB", makeutf8( album_get("LABEL_NAME") ) ), # The 'Publisher' frame simply contains the name of the label or publisher. | |
maketag( "TSIZ", makeutf8( str( FileSize )) ), # The 'Size' frame contains the size of the audiofile in bytes, excluding the ID3v2 tag, represented as a numeric string. | |
maketag( "TFLT", makeutf8( "MPG/3" ) ), | |
] # decimal, no term NUL | |
id3.extend( [ | |
maketag( ID_id3_frame, makeutf8( song_get(song, ID_song )) ) for (ID_id3_frame, ID_song) in \ | |
( | |
( "TALB", "ALB_TITLE" ), # The 'Album/Movie/Show title' frame is intended for the title of the recording(/source of sound) which the audio in the file is taken from. | |
( "TPE1", "ART_NAME" ), # The 'Lead artist(s)/Lead performer(s)/Soloist(s)/Performing group' is used for the main artist(s). They are seperated with the "/" character. | |
( "TPE2", "ART_NAME" ), # The 'Band/Orchestra/Accompaniment' frame is used for additional information about the performers in the recording. | |
( "TPOS", "DISK_NUMBER" ), # The 'Part of a set' frame is a numeric string that describes which part of a set the audio came from. This frame is used if the source described in the "TALB" frame is divided into several mediums, e.g. a double CD. The value may be extended with a "/" character and a numeric string containing the total number of parts in the set. E.g. "1/2". | |
( "TIT2", "SNG_TITLE" ), # The 'Title/Songname/Content description' frame is the actual name of the piece (e.g. "Adagio", "Hurricane Donna"). | |
( "TSRC", "ISRC" ), # The 'ISRC' frame should contain the International Standard Recording Code (ISRC) (12 characters). | |
) | |
]) | |
#try: | |
#id3.append( | |
#maketag( "APIC", makepic( | |
#downloadpicture( song["ALB_PICTURE"] ) | |
#) | |
#) | |
#) | |
#except Exception as e: | |
#print "no pic", e | |
id3data = b"".join(id3) | |
#> big-endian | |
#s char[] bytes | |
#H unsigned short integer 2 | |
#B unsigned char integer 1 | |
#L unsigned long integer 4 | |
hdr = struct.pack(">" | |
"3s" "H" "B" "L", | |
"ID3".encode("ascii"), | |
0x300, # version | |
0x00, # flags | |
make28bit( len( id3data) ) ) | |
fo.write(hdr) | |
fo.write(id3data) | |
#http://stackoverflow.com/a/295242/3135511 | |
# white list approach | |
# Not really useful - for ex. throws out german umlaute: | |
# like дьц aus well aus acent letters йбъ | |
# Attention this comment (with special letters) may trigger ask for how to encode this file when saving | |
# I fixed this by specifing | |
def FileNameClean_WL( FileName ): | |
safechars = \ | |
'_-.() ' + \ | |
string.digits + \ | |
string.ascii_letters | |
allchars = string.maketrans('', '') | |
outname = string.translate ( \ | |
FileName.encode('latin') , | |
allchars , | |
''.join( | |
set(allchars) - set(safechars) | |
) | |
) | |
return outname | |
def FileNameClean( FileName ): | |
return re.sub("[<>|?*]", "" ,FileName) \ | |
.replace('/', ',') \ | |
.replace(':', '-') #\ | |
#.replace('"', "'") \ | |
#.replace('<', "" ) \ | |
#.replace('>', "" ) \ | |
#.replace('|', "" ) \ | |
#.replace('?', "" ) \ | |
#.replace('*', "" ) | |
def download(args, song, fname=""): | |
""" download and save a song to a local file, given the json dict describing the song """ | |
urlkey = genurlkey( int(song.get("SNG_ID")), | |
str(song.get("MD5_ORIGIN")), | |
int(song.get("MEDIA_VERSION")), | |
getformat( song ) | |
) | |
key = calcbfkey( int(song["SNG_ID"]) ) | |
tracknum = ("%02i" % int(song["TRACK_NUMBER"])) \ | |
if "TRACK_NUMBER" in song \ | |
else "" | |
for i in ("ART_NAME", "ALB_TITLE", "SNG_TITLE", "SNG_ID"): | |
try: | |
exec("%s = str(song[\"%s\"])" %(i, i)) | |
except UnicodeEncodeError: | |
exec("%s = song[\"%s\"]" %(i, i)) | |
#outname = "%s - %s - %s%s - %010d.mp3" % ( | |
#str(song["ART_NAME"]), | |
#str(song["ALB_TITLE"]), | |
#tracknum, | |
#str(song["SNG_TITLE"]), | |
#int(song["SNG_ID"])) | |
if not fname: | |
outname = FileNameClean ("%s_%s - %s.mp3" % (tracknum, SNG_TITLE, ART_NAME)) | |
# Make DL dir | |
try: | |
os.makedirs( config_DL_Dir ) | |
except: | |
pass | |
outname = config_DL_Dir + "/%s" %outname | |
else: | |
outname = fname | |
if not args.overwrite and os.path.exists(outname): | |
print " already there: %s" % outname | |
return | |
try: | |
fh = urllib2.urlopen( (host_stream_cdn + "/%s") | |
% ( | |
str( song["MD5_ORIGIN"] )[0], | |
urlkey ) | |
) | |
with open(outname, "w+b") as fo: | |
# add songcover and DL first 30 sec's that are unencrypted | |
writeid3v2 ( fo, song) | |
decryptfile( fh, key, fo) | |
writeid3v1_1 ( fo, song) | |
############################################## | |
toWS = MP3( outname , ID3 = ID3) | |
try: | |
toWS.add_tags() | |
except: pass | |
toWS.tags.add( | |
APIC( | |
encoding = 3, # 3 is for utf-8 | |
mime = 'image/jpeg', # image/jpeg or image/png | |
type = 3, # 3 is for the cover image | |
desc = u'Cover', | |
data = downloadpicture( song["ALB_PICTURE"] ) | |
) | |
) | |
toWS.save( v2_version = 3 ) | |
except IOError as e: | |
print "IO_ERROR: %s" % (e) | |
raise | |
except Exception as e: | |
print "ERROR downloading from %s: %s" % (host_stream_cdn, e) | |
raise | |
def printinfo(song): | |
""" print info for a song """ | |
print "%9s %s %-5s %-30s %-30s %s" % ( | |
song["SNG_ID"], | |
song["MD5_ORIGIN"], | |
song["MEDIA_VERSION"], | |
song["ART_NAME"], | |
song["ALB_TITLE"], | |
song["SNG_TITLE"] | |
) | |
parser, args = (None, None) | |
def main(): | |
global parser, args | |
import argparse | |
parser = argparse.ArgumentParser(description='Deezer downloader') | |
parser.add_argument('--tor', '-T', action='store_true', help='Download via tor') | |
parser.add_argument('--list', '-l', action='store_true', help='Only list songs found on page') | |
parser.add_argument('--overwrite', '-f', action='store_true', help='Overwrite existing downloads') | |
parser.add_argument('urls', nargs='*', type=str) | |
args = parser.parse_args() | |
if args.tor: | |
enabletor() | |
if not args.urls: | |
mainExC() | |
for url in args.urls: | |
for song in parse_deezer_page(url): | |
if args.list: | |
printinfo(song) | |
else: | |
#print "...", song | |
try: | |
#raise Exception("Test Exception") | |
download(args, song) | |
except Exception as e: | |
print e | |
traceback.print_exc() | |
if "FALLBACK" in song: | |
try: | |
print "trying fallback" | |
download(args, song["FALLBACK"]) | |
except: | |
pass | |
#the download-track system is handled by the threading script | |
def scriptDownload(id, fname=None): | |
global act_threads, completedSongs, totalSongs, args | |
act_threads += 1 | |
progress(completedSongs * 100. / totalSongs, ' DL (total) > ') | |
url = "http://www.deezer.com/track/%s" %id | |
try: | |
song = parse_deezer_page(url).next() | |
except: | |
print " Could not find song; perhaps it isn't available here?" | |
downloaded = True | |
act_threads -= 1 | |
return downloaded | |
#print "...", song) | |
downloaded = False | |
try: | |
download(args, song, fname) | |
downloaded = True | |
except Exception as e: | |
print e | |
traceback.print_exc() | |
if not downloaded and "FALLBACK" in song: | |
try: | |
print "trying fall back" | |
download(args, song["FALLBACK"]) | |
downloaded = True | |
except: | |
pass | |
print ' Done!' + ' '*(59-len(' Done!')) | |
progress(completedSongs * 100. / totalSongs, ' DL (total) > ') | |
completedSongs += 1 | |
act_threads -= 1 | |
return downloaded | |
print '' | |
print '' | |
init() #Start Colorama's init' | |
def get_link(link): | |
for i in range(3): | |
try: | |
data = 'link=%s' %link | |
#print json.dumps({'link':'https://www.deezer.com/track/126884459'}) | |
#print type(json.dumps({'link':'https://www.deezer.com/track/126884459'})) | |
req = urllib2.Request('https://www.mp3fy.com/music/downloader.php') | |
req.add_header("User-Agent", "Mozilla/5.0 (X11; U; Linux i686) Gecko/20071127 Firefox/2.0.0.11") | |
response = urllib2.urlopen(req, data) | |
#print result) | |
retDict = json.loads(response.read()) | |
try: | |
return retDict['dlink'] | |
except KeyError: | |
return False | |
except ValueError: | |
print 'Okay... that\'s weird. Wait a momento...' | |
def load_songs(): | |
'Generate a list of songs from a playlist csv file.' | |
while True: | |
res = raw_input('Choose a mode - \n' | |
'(0) playlist (Song CSV)\n' | |
'(1) playlist (Deezer link)\n' | |
'(2) title or\n' | |
'(3) iTunes %i Top Charts ? > ' % config_topsongs_limit) | |
try: | |
if int(res) in (0, 1, 2, 3): | |
break | |
else: continue | |
except: | |
continue | |
if int(res) == 0: | |
print '''WARNING! | |
Using the CSV playlist function can be inaccurate and cumbersome. Also, if you don't have bs4 it will crash.' | |
Try using the Deezer playlist instead!''' | |
try: | |
#file_path = 'playlist.csv' # FOR EXPERIMENTAL PURPOSES, PLEASE REMOVE | |
file_path = sys.argv[1] # FOR EXPERIMENTAL PURPOSES, PLEASE UNCOMMENT | |
except IndexError: | |
print 'You need to choose a .csv file to use. If you don\'t have one, '\ | |
'get one from <http://joellehman.com/playlist/>!' | |
root = __import__('Tkinter').Tk() | |
root.withdraw() | |
root.update() | |
file_path = openfile(title='Choose a .csv file') | |
print file_path | |
try: | |
mFile = open(file_path, 'r') | |
sPlaylist = csv.reader(mFile) | |
return sPlaylist, False | |
except IOError: | |
return 'NoFile' | |
elif int(res) == 1: | |
deezerLink = raw_input('What\'s the link/ID to the Deezer playlist? > ') | |
deezerAPILink = 'http://api.deezer.com/playlist/%s' %deezerLink.split('/')[-1] | |
#print deezerAPILink | |
#link_data_json = urllib2.urlopen(deezerAPILink).read() | |
try: | |
link_data_json = urllib2.urlopen(deezerAPILink).read() | |
except: | |
print 'Oops! Did something go wrong? Check your link...' | |
sys.exit('FAIL') | |
#print link_data_json | |
link_data = json.loads(link_data_json) | |
if 'error' in link_data: | |
print 'Something went wrong. Check your link...' | |
sys.exit('FAIL') | |
#print link_data | |
#print link_data['tracks']['data'] | |
sPlaylist = [] | |
for trackData in link_data['tracks']['data']: | |
sPlaylist.append((trackData['title'], | |
trackData['artist']['name'], | |
trackData['link'])) | |
#Print sPlaylist | |
return ['JUNK'] + sPlaylist, True | |
elif int(res) == 2: | |
track = raw_input('Song name? > ') | |
artist = raw_input('Artist name? > ') | |
return deezerSearch(track, artist) | |
# return ('JUNK_DISCARD',(song_name, track)) | |
else: | |
iTunesText = ('iTunes country code for your country?\n' | |
'(AU) for Australia\n' | |
'(US) for the United States\n' | |
'(GB) for Great Britain) > ') | |
if sys.version_info[0] == 2: | |
countryCode = raw_input(iTunesText) | |
else: | |
countryCode = input(iTunesText) | |
countryCode = countryCode.lower() | |
feedlink = "http://itunes.apple.com/%s/rss/topsongs/limit=%i/explicit=true/xml" % \ | |
(countryCode, config_topsongs_limit) | |
feed = feedparser.parse(feedlink) | |
if feed["bozo"]: | |
input("Not a valid country code.") | |
sys.exit() | |
songslist = [] | |
for item in feed["items"]: | |
title = unicodedata.normalize('NFKD', u"%s" %(item["title"])) \ | |
.encode('ascii', 'ignore') | |
#print title | |
title = "".join ( title .split(" - ")[:-1] ) | |
artist = item["title"] .split(" - ")[ -1] | |
#I Feel It Coming (feat. Daft Punk) - The Weeknd | |
#I Feel It Coming | |
#I Dont Wanna Live Forever (Fifty Shades Darker) - ZAYN & Taylor Swift | |
#I Dont Wanna Live Forever (Fi | |
#I Don\u2019t Wann... | |
phrases = ("feat\.", "ft\.") | |
modifiers = ( | |
(r"\(" , r"\)" ), | |
( "" , "" ), | |
( " " , "" ), | |
( " " , " " ) | |
) | |
for ph in phrases: | |
for mod in modifiers: | |
title = re.sub( mod[0] + ph + ".+" + mod[1], "", title) | |
print title | |
title = title.strip() | |
songslist.append( [title, artist] ) | |
return ["JUNK"] + songslist, False | |
def download_songs(sPlaylist, url=False): | |
'Download songs, using the Deezer search engine.' | |
global completedSongs, totalSongs, act_threads | |
currentSong = 1 | |
green = Fore.GREEN | |
black = Back.BLACK | |
userOpin = raw_input('Do you want to number the songs? [Y/N]') | |
userOpin = userOpin.lower().startswith('y') | |
print Fore.RED + "Downloading to '" + config_DL_Dir + "'" | |
print green + 'Starting download with Deezer' | |
first = True | |
second = False | |
completedSongs = 0 | |
totalSongs = len(sPlaylist)-1 | |
startProgress(' DL (total) > ') | |
unchUrl = copy.deepcopy(url) | |
#progress(file_size_dl * 100. / file_size_int, ' DL (total) > ') | |
#endProgress(' DL (done!)> ') | |
for song in sPlaylist: | |
while act_threads > 4: | |
time.sleep(0.5) | |
if first: | |
first = False | |
second = True | |
continue | |
if second: | |
second = False | |
print green + ' Song: %s' %song[0] + ' '*(59-len(' Song: %s' %song[0])) | |
progress( completedSongs * 100. / totalSongs, ' DL (total) > ') | |
if userOpin: | |
sTitle = '%s. %s' %(currentSong, song[0]) | |
else: | |
sTitle = song[0] | |
file_name = 'downloads/%s - %s.mp3' %( | |
sTitle.rstrip(), | |
song[1].rstrip() | |
) | |
file_name = file_name.rstrip() | |
if os.path.exists(file_name): | |
print ' Exists already. Skipping...' + ' '*(59-\ | |
len (' Exists already. Skipping...')) | |
progress(completedSongs * 100. / totalSongs, ' DL (total) > ') | |
currentSong += 1 | |
continue | |
if not unchUrl: | |
songTitle = song[0] | |
artistName = song[1] | |
url = 'http://api.deezer.com/search?q=%s%%20%s' %( | |
MakeUrlItem( songTitle), | |
MakeUrlItem(artistName) ) | |
html_mpfy = urllib2.urlopen( url ).read() | |
js = json.loads(html_mpfy) | |
try: | |
downloadLink = js["data"][0]["id"] | |
#scriptDownload(downloadId, file_name) | |
if userOpin: | |
dw_thr = threading.Thread(target=scriptDownload, args=(downloadLink, file_name)) | |
else: | |
dw_thr = threading.Thread(target=scriptDownload, args=(downloadLink, )) | |
dw_thr.start() | |
except IndexError: | |
print url | |
print green + ' Song not found. Skipping...' + ' '*(59-len(' Song not found. Skipping...')) | |
progress(completedSongs * 100. / totalSongs, ' DL (total) > ') | |
else: | |
try: | |
link = re.findall(r"track/(\d*)", song[2])[0] | |
except IndexError: | |
link = "" | |
if link: | |
dw_thr = threading.Thread(target=scriptDownload, args=(link, )) | |
#download_file(link, sTitle, song[1]) | |
dw_thr.start() | |
else: | |
print ' Skipping... (Could not download)' + \ | |
' '*(59-len(' Skipping... (Could not download)')) | |
progress(completedSongs * 100. / totalSongs, ' DL (total) > ') | |
currentSong += 1 | |
progress(completedSongs * 100. / totalSongs, ' DL (total) > ') | |
while True: | |
if act_threads == 1: | |
break | |
time.sleep(0.5) | |
endProgress(' DL (done!)> ') | |
def startProgress(title): | |
to_write = title + ": [" + "-"*40 + "]" | |
sys.stdout.write(to_write + chr(8)*len(to_write)) | |
sys.stdout.flush() | |
def progress(x, title): | |
x_t = int(x * 40 // 100) | |
to_write = title + ": [" + "#"*x_t + "-"*(40-x_t) + "]" | |
#sys.stdout.write("#" * (x - progress_x)) | |
sys.stdout.write(to_write + chr(8)*len(to_write)) | |
sys.stdout.flush() | |
def endProgress(title): | |
to_write = title + ": [" + "#"*40 + "]\n" | |
sys.stdout.write(to_write) | |
sys.stdout.flush() | |
def MakeUrlItem(item): | |
try: | |
return '+'.join(urllib.quote_plus(item).split(' ')) | |
except: | |
return '' | |
def deezerSearch(title, artist=''): | |
'Searches for a song using the Deezer API.' | |
query = 'http://api.deezer.com/search?q=%s%s%s' % \ | |
( MakeUrlItem( title) , \ | |
r'%20', \ | |
MakeUrlItem( artist ) \ | |
) | |
#print query | |
qResultRaw = urllib2.urlopen( | |
query).read() | |
cnt = 1 | |
#print qResultRaw | |
try: | |
qResult = json.loads(qResultRaw)['data'] | |
except ValueError: | |
print 'Things went wrong. There was NOTHING returned for your search! :O' | |
return None | |
cnt = 0 | |
group = 0 | |
print Fore.RESET + 'Press the KEY to select, or any other to continue' | |
print Fore.RED + Back.WHITE + 'KEY:\tTRACK - ARTIST' | |
for i in qResult: | |
try: | |
if group and group*5 + cnt >= len(qResult): | |
print Fore.RESET + 'Looks like the end! Sorry...' | |
if not group: | |
print Fore.RED + Back.WHITE + '%s:\t%s - %s' %(cnt+1, qResult[cnt]['title'], qResult[cnt]['artist']['name']) | |
else: | |
print Fore.RED + Back.WHITE + '%s:\t%s - %s' %(cnt+1, qResult[(group*5-1) + cnt]['title'], qResult[(group*5-1) + cnt]['artist']['name']) | |
except: | |
print Fore.RESET + 'Error' | |
if cnt >= 4 or group*5 + cnt + 1 == len(qResult): # NEED TO FINISH | |
#print range(1, cnt+2) | |
q = raw_input(Fore.RESET + Back.RESET + ' Which song? ') | |
if q in [str(x) for x in range(1, cnt+2)]: | |
cnNum = int(q)-1 | |
if not group: | |
qr = qResult[cnNum] | |
#userOpin = raw_input(Fore.RESET + ' Chosen: %s by %s. Confirm? ' %(qr['title'], qResult[cnNum]['artist']['name'])) | |
if True: #userOpin.lower().startswith('y'): | |
result = previewSong(qr) | |
if result: | |
print "NOTE: Due to the nature of the script, the progress bar will not move until the whole song is finished downloading (it's made for downloading with a playist.)" | |
return ['JUNK',[qr['title'], | |
qr['artist']['name'], | |
qr['link']]], True | |
else: | |
qr = qResult[(group*5-1)+cnNum] | |
#userOpin = raw_input(' Chosen: %s by %s. Confirm? ' %(\ | |
# qr['title'], | |
# qr['artist']['name'])) | |
if True:#userOpin.lower().startswith('y'): | |
result = previewSong(qr) | |
if result: | |
print "NOTE: Due to the nature of the script, the progress bar will not move until the whole song is finished downloading (it's made for downloading with a playist.)" | |
return ['JUNK',[qr['title'], | |
qr['artist']['name'], | |
qr['link']]], True | |
print Fore.RESET + 'Press the KEY to select, or any other to continue' | |
print Fore.RED + 'KEY:\tTRACK - ARTIST' | |
group += 1 | |
cnt = -1 | |
cnt += 1 | |
print 'Looks like the end...' | |
sys.exit('FAIL') | |
def previewSong(qPlaylist): | |
'Creates a preview of the song' | |
urllib.urlretrieve(qPlaylist['preview'], 'temp.mp3') | |
#with open('temp.mp3','w') as tempMedia: | |
#content = urllib2.urlopen(qPlaylist['preview']).read() | |
#print qPlaylist['preview'] | |
#tempMedia.write(content) | |
player = pyglet.media.Player() | |
player.queue(pyglet.media.load('temp.mp3')) | |
player.play() | |
userOpin = raw_input(' Are you sure you want to choose this song? ') | |
player.pause() | |
del player | |
if userOpin.lower().startswith('y'): | |
return True | |
def mainExC(): | |
'Run the program as an executable' | |
global act_threads | |
sPlaylist, oldSearch = load_songs() | |
if sPlaylist: | |
download_songs(sPlaylist, oldSearch) | |
return 'SUCCESS' | |
else: | |
return 'FAIL' | |
act_threads = 1 | |
completedSongs = 0 | |
totalSongs = 0 | |
if __name__ == '__main__': | |
sys.exit( main() ) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
In the
calcbfkey
function, how did you manage to find how to build the key ?