Kartom

DeezerDownload

Feb 24th, 2017 (edited)
1,524
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 36.04 KB | None | 0 0
  1. #!python2
  2. #coding:latin
  3. """
  4.  Author:   --<>
  5.  Purpose:
  6.     Download and decrypt songs from deezer.
  7.     The song is saved as a mp3.
  8.    
  9.     No ID3 tags are added to the file.
  10.     The filename contains album, artist, song title.
  11.    
  12.     Usage:
  13.    
  14.     python DeezerDownload.py http://www.deezer.com/album/6671241/
  15.     python DeezerDownload.py
  16.    
  17.     This will create mp3's in the '\downloads' directory, with the song information in the filenames.
  18.  Created: 16.02.2017
  19.  
  20. """
  21.  
  22. config_DL_Dir           = 'downloads'
  23. config_topsongs_limit   = 50
  24.  
  25. import sys
  26. from Crypto.Hash import MD5
  27. from Crypto.Cipher import AES, Blowfish
  28. import re
  29. import os
  30. import json
  31. import struct
  32. import urllib
  33. import urllib2
  34. import HTMLParser
  35. import copy
  36. import traceback
  37. import csv
  38. import threading
  39. import time
  40. import httplib
  41. import requests
  42. from mutagen.mp3 import MP3
  43. from mutagen.id3 import ID3, APIC, error
  44. from decimal import Decimal
  45. from colorama import Fore, Back, Style, init
  46. from tkFileDialog import askopenfilename as openfile
  47. import pyglet
  48. import feedparser
  49. import unicodedata
  50. from binascii import a2b_hex, b2a_hex
  51.  
  52. import string
  53.  
  54. #load AVBin
  55. try:
  56.     pyglet.lib.load_library('avbin')
  57. except Exception as e:
  58.     print 'Trying to load avbin64...'
  59.     pyglet.lib.load_library('avbin64')
  60.     print 'Success!'
  61.  
  62.  
  63. pyglet.have_avbin=True
  64.  
  65. # global variable
  66. host_stream_cdn = "http://e-cdn-proxy-%s.deezer.com/mobile/1"
  67. setting_domain_img = "http://cdn-images.deezer.com/images"
  68.  
  69.  
  70. httplib.HTTPConnection._http_vsn = 10
  71. httplib.HTTPConnection._http_vsn_str = 'HTTP/1.0'
  72.  
  73.  
  74. def enabletor():
  75.     import socks
  76.     import socket
  77.  
  78.     def create_connection(address, timeout=None, source_address=None):
  79.         sock = socks.socksocket()
  80.         sock.connect(address)
  81.         return sock
  82.  
  83.     socks.setdefaultproxy(socks.PROXY_TYPE_SOCKS5, "127.0.0.1", 9050)
  84.  
  85.     # patch the socket module
  86.     socket.socket = socks.socksocket
  87.     socket.create_connection = create_connection
  88.  
  89.     # todo: test if tor connection really works by connecting to https://check.torproject.org/
  90.  
  91. class ScriptExtractor(HTMLParser.HTMLParser):
  92.     """ extract <script> tag contents from a html page """
  93.     def __init__(self):
  94.         HTMLParser.HTMLParser.__init__(self)
  95.         self.scripts = []
  96.         self.curtag = None
  97.  
  98.     def handle_starttag(self, tag, attrs):
  99.         self.curtag = tag.lower()
  100.  
  101.     def handle_data(self, data):
  102.         if self.curtag == "script":
  103.             self.scripts.append(data)
  104.  
  105.     def handle_endtag(self, tag):
  106.         self.curtag = None
  107.  
  108.  
  109. def find_re(txt, regex):
  110.     """ Return either the first regex group, or the entire match """
  111.     m = re.search(regex, txt)
  112.     if not m:
  113.         return
  114.     gr = m.groups()
  115.     if gr:
  116.         return gr[0]
  117.     return m.group()
  118.  
  119.  
  120. def find_songs(obj):
  121.     """ recurse into json object, yielding all objects which look like song descriptions """
  122.     if type( obj ) == list:
  123.        
  124.         for item in obj:
  125.            
  126.             for tItem in find_songs(item):
  127.                 yield tItem
  128.             #yield from find_songs(item)
  129.            
  130.     elif type( obj ) == dict:
  131.        
  132.         if "SNG_ID" in obj:
  133.             yield obj
  134.            
  135.         for v in obj.values():
  136.            
  137.             for tItem in find_songs(v):
  138.                 yield tItem
  139.             #yield from find_songs(v)
  140.  
  141.  
  142. def parse_deezer_page(url):
  143.     """
  144.    extracts download host, and yields any songs found in a page.
  145.    """
  146.     f = urllib2.urlopen(url)
  147.     data = f.read()
  148.  
  149.     parser = ScriptExtractor()
  150.     parser.feed(data.decode('utf-8'))
  151.     parser.close()
  152.  
  153.     # note: keeping track of songs by songid, this might still lead to duplicate downloads,
  154.     # for instance when the same song is present on multiple albums.
  155.     # if you want to avoid that, add the MD5_ORIGIN to the foundsongs set, instead of the SNG_ID.
  156.     foundsongs = set()
  157.     for script in parser.scripts:
  158.        
  159.       # http://e-cdn-proxy-%s.deezer.com/mobile/1/  
  160.         var = find_re(script, r'var HOST_STREAM_CDN\s*=\s*\'(.*?)\';')
  161.         if var:
  162.             global host_stream_cdn
  163.             host_stream_cdn = var.replace("{0}", "%s")
  164.  
  165.       # http://cdn-images.deezer.com/images
  166.         var = find_re(script, r'var SETTING_DOMAIN_IMG\s*=\s*\'(.*?)\';')
  167.         if var:
  168.             global setting_domain_img
  169.             setting_domain_img = var
  170.      #.*MD5_ORIGIN -> PLAYER_INIT & __DZR_APP_STATE__
  171.      #.*LABEL_NAME -> __DZR_APP_STATE__
  172.  
  173.         jsondata = find_re(script, r'\{.*LABEL_NAME.*\}')
  174.         if jsondata:
  175.            
  176.             DZR_APP_STATE = json.loads( jsondata )
  177.            
  178.             global album_Data
  179.             album_Data = DZR_APP_STATE.get("DATA")
  180.            
  181.             # Add num of tracks
  182.             try:
  183.                 album_Data["TRACKS"] = DZR_APP_STATE["SONGS"]["total"]
  184.             except:
  185.                 pass
  186.            
  187.             for song in find_songs( DZR_APP_STATE.get("SONGS") ):
  188.                 if song["SNG_ID"] not in foundsongs:
  189.                     yield song
  190.                     foundsongs.add(song["SNG_ID"])
  191.  
  192.  
  193. def md5hex(data):
  194.     """ return hex string of md5 of the given string """
  195.     h = MD5.new()
  196.     h.update( data)
  197.     return b2a_hex( h.digest() )
  198.  
  199.  
  200. def hexaescrypt(data, key):
  201.     """ returns hex string of aes encrypted data """
  202.     c = AES.new( key, AES.MODE_ECB)
  203.     return b2a_hex( c.encrypt(data) )
  204.  
  205.  
  206. def genurlkey( songid, md5origin, mediaver=4, fmt=1):
  207.     """ Calculate the deezer download url given the songid, origin and media+format """
  208.     data = '\xa4'.join(_.encode("utf-8") for _ in [
  209.         md5origin,
  210.         str( fmt ),
  211.         str( songid ),
  212.         str( mediaver )
  213.     ])
  214.    
  215.     data = '\xa4'.join( [ md5hex(data), data ] ) + '\xa4'
  216.    
  217.     if len(data)%16:
  218.         data += b'\0' * (16-len(data)%16)
  219.        
  220.     return hexaescrypt(data, "jo6aey6haid2Teih" ).decode('utf-8')
  221.  
  222.  
  223. def calcbfkey(songid):
  224.     """ Calculate the Blowfish decrypt key for a given songid """
  225.     h = md5hex( "%d" % songid)
  226.     key = "g4el58wc0zvf9na1"
  227.    
  228.     return "".join(
  229.         chr(
  230.             ord( h[ i ]     ) ^
  231.             ord( h[ i + 16] ) ^
  232.             ord( key[i]     )
  233.             ) for i in range( 16 )
  234.     )
  235.  
  236.  
  237. def blowfishDecrypt(data, key):
  238.     """ CBC decrypt data with key """
  239.     c = Blowfish.new( key ,
  240.                       Blowfish.MODE_CBC,
  241.                       a2b_hex( "0001020304050607" )
  242.                       )
  243.     return c.decrypt(data)
  244.  
  245.  
  246. def decryptfile(fh, key, fo):
  247.     """
  248.    Decrypt data from file <fh>, and write to file <fo>.
  249.    decrypt using blowfish with <key>.
  250.    Only every third 2048 byte block is encrypted.
  251.    """
  252.     blockSize = 0x800 #2048 byte
  253.     i = 0
  254.    
  255.     while True:
  256.         data = fh.read( blockSize )
  257.         if not data:
  258.             break
  259.  
  260.         isEncrypted  = ( (i % 3) == 0 )
  261.         isWholeBlock = len(data) == blockSize
  262.        
  263.         if isEncrypted and isWholeBlock:
  264.             data = blowfishDecrypt(data, key)
  265.            
  266.         fo.write(data)
  267.         i += 1
  268.  
  269.  
  270.  
  271. ## Built-in namespace
  272. #import __builtin__
  273.  
  274. ## Extended dict
  275. #class myDict(dict):
  276.    
  277.     #def s(self, key):
  278.         #""" return value as UTF-8 String"""
  279.         #if self:
  280.             #return self.get(key).encode('utf-8')
  281.         #else:
  282.             #return ''
  283.  
  284. ## Substitute the original str with the subclass on the built-in namespace    
  285. #__builtin__.dict = myDict
  286. ##type bugfix
  287. #dict = type( {} )
  288.  
  289. def getformat(song):
  290.     """ return format id for a song """
  291.     return 3 if song.get("FILESIZE_MP3_320") else \
  292.            5 if song.get("FILESIZE_MP3_256") else \
  293.            1
  294. #FILESIZE_MP3_128 FILESIZE_MP3_64 FILESIZE_AAC_64
  295.  
  296.  
  297. def writeid3v1_1(fo, song):
  298.    
  299.     # Bugfix changed song["SNG_TITLE... to song.get("SNG_TITLE... to avoid 'key-error' in case the key does not exist
  300.     def song_get(song, key):
  301.         try:
  302.             return song.get(key).encode('utf-8')
  303.         except Exception as e:
  304.             return ""
  305.        
  306.     def album_get(key):
  307.         global album_Data
  308.         try:
  309.             return album_Data.get(key).encode('utf-8')
  310.         except Exception as e:
  311.             return ""    
  312.            
  313.     data = struct.pack("3s" "30s" "30s" "30s" "4s" "28sB" "B"  "B",
  314.                        "TAG",                                             # header
  315.                        song_get (song, "SNG_TITLE"),                             # title
  316.                        song_get (song, "ART_NAME") ,                             # artist
  317.                        song_get (song, "ALB_TITLE"),                             # album
  318.                        album_get("PHYSICAL_RELEASE_DATE"),                # year
  319.                        album_get("LABEL_NAME"), 0,                        # comment
  320.                        
  321.                        int(song_get(song, "TRACK_NUMBER") or 0),                # tracknum
  322.                        255                                                # genre
  323.                     )
  324.     fo.write( data )
  325.  
  326. def downloadpicture(id):
  327.     try:        
  328.        
  329.         fh = urllib2.urlopen(
  330.             setting_domain_img + "/cover/" + id + "/1200x1200.jpg"
  331.         )
  332.         return fh.read()
  333.    
  334.     except Exception as e:
  335.         print "no pic", e
  336.    
  337.  
  338. def writeid3v2(fo, song):
  339.    
  340.     def make28bit(x):
  341.         return  (
  342.             (x<<3) & 0x7F000000) | (
  343.             (x<<2) &   0x7F0000) | (
  344.             (x<<1) &     0x7F00) | \
  345.             (x     &       0x7F)
  346.    
  347.     def maketag(tag, content):
  348.         return struct.pack( ">4sLH",
  349.                              tag.encode( "ascii" ),
  350.                              len(content),
  351.                              0
  352.                            ) + content
  353.  
  354.     def album_get(key):
  355.         global album_Data
  356.         try:
  357.             return album_Data.get(key)#.encode('utf-8')
  358.         except Exception as e:
  359.             return ""  
  360.        
  361.     def song_get(song, key):
  362.         try:
  363.             return song[key]#.encode('utf-8')
  364.         except Exception as e:
  365.             return ""    
  366.    
  367.     def makeutf8(txt):
  368.         return b"\x03" +  txt .encode('utf-8')
  369.    
  370.     def makepic(data):
  371.         # Picture type:
  372.         # 0x00     Other
  373.         # 0x01     32x32 pixels 'file icon' (PNG only)
  374.         # 0x02     Other file icon
  375.         # 0x03     Cover (front)
  376.         # 0x04     Cover (back)
  377.         # 0x05     Leaflet page
  378.         # 0x06     Media (e.g. lable side of CD)
  379.         # 0x07     Lead artist/lead performer/soloist
  380.         # 0x08     Artist/performer
  381.         # 0x09     Conductor
  382.         # 0x0A     Band/Orchestra
  383.         # 0x0B     Composer
  384.         # 0x0C     Lyricist/text writer
  385.         # 0x0D     Recording Location
  386.         # 0x0E     During recording
  387.         # 0x0F     During performance
  388.         # 0x10     Movie/video screen capture
  389.         # 0x11     A bright coloured fish
  390.         # 0x12     Illustration
  391.         # 0x13     Band/artist logotype
  392.         # 0x14     Publisher/Studio logotype        
  393.         imgframe = ( "\x00",                 # text encoding
  394.                      "image/jpeg",  "\0",    # mime type
  395.                      "\x03",                 # picture type: 'Cover (front)'
  396.                      ""[:64],  "\0",         # description
  397.                      data
  398.                     )
  399.        
  400.         return b'' .join( imgframe )
  401.  
  402.    
  403.     # get Data as DDMM
  404.     try:
  405.         phyDate_YYYYMMDD = album_get("PHYSICAL_RELEASE_DATE") .split('-') #'2008-11-21'
  406.         phyDate_DDMM    = phyDate_YYYYMMDD[2] + phyDate_YYYYMMDD[1]
  407.     except:
  408.         phyDate_DDMM    = ''
  409.    
  410.     # get size of first item in the list that is not 0
  411.     try:
  412.         FileSize = [
  413.             song_get(song,i)
  414.             for i in (
  415.                 'FILESIZE_AAC_64',
  416.                 'FILESIZE_MP3_320',
  417.                 'FILESIZE_MP3_256',
  418.                 'FILESIZE_MP3_64',
  419.                 'FILESIZE',
  420.                 ) if song_get(song,i)
  421.             ][0]
  422.     except:
  423.         FileSize    = 0
  424.    
  425.     try:
  426.         track = "%02s" % song["TRACK_NUMBER"]
  427.         track += "/%02s" % album_get("TRACKS")
  428.     except:
  429.         pass
  430.    
  431.     # http://id3.org/id3v2.3.0#Attached_picture
  432.     id3 = [
  433.         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".
  434.         maketag( "TLEN", makeutf8( str( int(song["DURATION"]) * 1000 )          ) ),     # The 'Length' frame contains the length of the audiofile in milliseconds, represented as a numeric string.
  435.         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
  436.         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).
  437.         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.
  438.         maketag( "TPUB", makeutf8( album_get("LABEL_NAME")                ) ),     # The 'Publisher' frame simply contains the name of the label or publisher.
  439.         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.
  440.         maketag( "TFLT", makeutf8( "MPG/3"                                ) ),
  441.        
  442.         ]  # decimal, no term NUL
  443.     id3.extend( [
  444.         maketag( ID_id3_frame, makeutf8( song_get(song, ID_song ))  ) for (ID_id3_frame, ID_song) in \
  445.         (
  446.             ( "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.
  447.             ( "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.
  448.             ( "TPE2", "ART_NAME"    ),   # The 'Band/Orchestra/Accompaniment' frame is used for additional information about the performers in the recording.
  449.             ( "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".
  450.             ( "TIT2", "SNG_TITLE"   ),   # The 'Title/Songname/Content description' frame is the actual name of the piece (e.g. "Adagio", "Hurricane Donna").
  451.             ( "TSRC", "ISRC"        ),   # The 'ISRC' frame should contain the International Standard Recording Code (ISRC) (12 characters).
  452.         )
  453.     ])
  454.  
  455.     #try:
  456.         #id3.append(
  457.             #maketag( "APIC", makepic(
  458.                         #downloadpicture( song["ALB_PICTURE"] )
  459.                     #)
  460.             #)
  461.         #)
  462.     #except Exception as e:
  463.         #print "no pic", e
  464.  
  465.     id3data = b"".join(id3)
  466. #>  big-endian
  467. #s  char[]  bytes
  468. #H  unsigned short  integer 2
  469. #B  unsigned char   integer 1
  470. #L  unsigned long   integer 4
  471.  
  472.  
  473.     hdr = struct.pack(">"
  474.                       "3s" "H" "B" "L",
  475.                       "ID3".encode("ascii"),
  476.                       0x300,   # version
  477.                       0x00,    # flags
  478.                       make28bit( len( id3data) ) )
  479.  
  480.     fo.write(hdr)
  481.     fo.write(id3data)
  482.  
  483.  
  484. #http://stackoverflow.com/a/295242/3135511
  485. # white list approach
  486. #   Not really useful - for ex. throws out german umlaute:
  487. #      like дьц aus well aus acent letters йбъ
  488. #      Attention this comment (with special letters) may trigger ask for how to encode this file when saving
  489. #      I fixed this by specifing
  490. def FileNameClean_WL( FileName ):
  491.  
  492.     safechars = \
  493.         '_-.() ' + \
  494.         string.digits + \
  495.         string.ascii_letters
  496.    
  497.     allchars  = string.maketrans('', '')
  498.  
  499.     outname = string.translate ( \
  500.         FileName.encode('latin') ,
  501.         allchars ,
  502.         ''.join(
  503.             set(allchars) - set(safechars)
  504.             )
  505.     )
  506.     return outname
  507.    
  508. def FileNameClean( FileName ):
  509.     return re.sub("[<>|?*]", "" ,FileName)  \
  510.         .replace('/', ',') \
  511.         .replace(':', '-') #\
  512.         #.replace('"', "'") \
  513.         #.replace('<', "" ) \
  514.         #.replace('>', "" ) \
  515.         #.replace('|', "" ) \
  516.         #.replace('?', "" ) \
  517.         #.replace('*', "" )
  518.  
  519.  
  520.  
  521.  
  522. def download(args, song, fname=""):
  523.     """ download and save a song to a local file, given the json dict describing the song """
  524.  
  525.  
  526.     urlkey = genurlkey( int(song.get("SNG_ID")),
  527.                         str(song.get("MD5_ORIGIN")),
  528.                         int(song.get("MEDIA_VERSION")),
  529.                         getformat( song )
  530.                         )
  531.     key = calcbfkey( int(song["SNG_ID"]) )
  532.  
  533.     tracknum = ("%02i" % int(song["TRACK_NUMBER"])) \
  534.                  if "TRACK_NUMBER" in song \
  535.                  else ""
  536.  
  537.     for i in ("ART_NAME", "ALB_TITLE", "SNG_TITLE", "SNG_ID"):
  538.         try:
  539.             exec("%s = str(song[\"%s\"])" %(i, i))
  540.         except UnicodeEncodeError:
  541.             exec("%s = song[\"%s\"]" %(i, i))
  542.     #outname = "%s - %s - %s%s - %010d.mp3" % (
  543.     #str(song["ART_NAME"]),
  544.     #str(song["ALB_TITLE"]),
  545.     #tracknum,
  546.     #str(song["SNG_TITLE"]),
  547.     #int(song["SNG_ID"]))
  548.    
  549.     if not fname:
  550.         outname = FileNameClean ("%s_%s - %s.mp3" % (tracknum, SNG_TITLE, ART_NAME))
  551.        
  552.       # Make DL dir
  553.         try:
  554.             os.makedirs( config_DL_Dir )
  555.         except:
  556.             pass
  557.        
  558.         outname = config_DL_Dir + "/%s" %outname
  559.     else:
  560.         outname = fname
  561.  
  562.     if not args.overwrite and os.path.exists(outname):
  563.         print "        already there: %s" % outname
  564.         return
  565.     try:
  566.         fh = urllib2.urlopen( (host_stream_cdn + "/%s")
  567.                               % (
  568.                                   str( song["MD5_ORIGIN"] )[0],
  569.                                   urlkey )
  570.                               )
  571.        
  572.         with open(outname, "w+b") as fo:
  573.            
  574.           # add songcover and DL first 30 sec's that are unencrypted            
  575.             writeid3v2 ( fo, song)
  576.            
  577.             decryptfile( fh, key, fo)
  578.            
  579.             writeid3v1_1 ( fo, song)
  580.            
  581.         ##############################################
  582.         toWS = MP3( outname , ID3 = ID3)
  583.        
  584.         try:
  585.             toWS.add_tags()
  586.         except: pass
  587.    
  588.         toWS.tags.add(
  589.             APIC(
  590.                 encoding = 3,        # 3 is for utf-8
  591.                 mime = 'image/jpeg', # image/jpeg or image/png
  592.                 type = 3,            # 3 is for the cover image
  593.                 desc = u'Cover',
  594.                 data = downloadpicture( song["ALB_PICTURE"] )
  595.             )
  596.         )
  597.         toWS.save( v2_version = 3 )
  598.            
  599.            
  600.  
  601.     except IOError as e:
  602.         print "IO_ERROR: %s" % (e)
  603.         raise        
  604.        
  605.     except Exception as e:
  606.         print "ERROR downloading from %s: %s" % (host_stream_cdn, e)
  607.         raise
  608.  
  609.  
  610. def printinfo(song):
  611.     """ print info for a song """
  612.     print "%9s %s %-5s %-30s %-30s %s" % (
  613.         song["SNG_ID"],
  614.         song["MD5_ORIGIN"],
  615.         song["MEDIA_VERSION"],
  616.         song["ART_NAME"],
  617.         song["ALB_TITLE"],
  618.         song["SNG_TITLE"]
  619.     )
  620.  
  621.  
  622. parser, args = (None, None)
  623. def main():
  624.     global parser, args
  625.  
  626.     import argparse
  627.     parser = argparse.ArgumentParser(description='Deezer downloader')
  628.     parser.add_argument('--tor', '-T', action='store_true', help='Download via tor')
  629.     parser.add_argument('--list', '-l', action='store_true', help='Only list songs found on page')
  630.     parser.add_argument('--overwrite', '-f', action='store_true', help='Overwrite existing downloads')
  631.     parser.add_argument('urls', nargs='*', type=str)
  632.     args = parser.parse_args()
  633.  
  634.     if args.tor:
  635.         enabletor()
  636.  
  637.     if not args.urls:
  638.         mainExC()
  639.  
  640.     for url in args.urls:
  641.         for song in parse_deezer_page(url):
  642.             if args.list:
  643.                 printinfo(song)
  644.             else:
  645.                 #print "...", song
  646.                 try:
  647.                     #raise Exception("Test Exception")
  648.                     download(args, song)
  649.                 except Exception as e:
  650.                     print e
  651.                     traceback.print_exc()
  652.                     if "FALLBACK" in song:
  653.                         try:
  654.                             print "trying fallback"
  655.                             download(args, song["FALLBACK"])
  656.                         except:
  657.                             pass
  658.                     #the download-track system is handled by the threading script
  659.  
  660.  
  661. def scriptDownload(id, fname=None):
  662.     global act_threads, completedSongs, totalSongs, args
  663.  
  664.     act_threads += 1
  665.     progress(completedSongs * 100. / totalSongs, '  DL (total) > ')
  666.     url = "http://www.deezer.com/track/%s" %id
  667.     try:
  668.         song = parse_deezer_page(url).next()
  669.     except:
  670.         print "        Could not find song; perhaps it isn't available here?"
  671.         downloaded = True
  672.         act_threads -= 1
  673.         return downloaded
  674.     #print "...", song)
  675.     downloaded = False
  676.     try:
  677.         download(args, song, fname)
  678.         downloaded = True
  679.     except Exception as e:
  680.         print e
  681.         traceback.print_exc()
  682.     if not downloaded and "FALLBACK" in song:
  683.         try:
  684.             print "trying fall back"
  685.             download(args, song["FALLBACK"])
  686.             downloaded = True
  687.         except:
  688.             pass
  689.     print '    Done!' + ' '*(59-len('    Done!'))
  690.     progress(completedSongs * 100. / totalSongs, '  DL (total) > ')
  691.     completedSongs += 1
  692.     act_threads -= 1
  693.     return downloaded
  694.  
  695.  
  696. print ''
  697. print ''
  698.  
  699.  
  700. init() #Start Colorama's init'
  701.  
  702. def get_link(link):
  703.     for i in range(3):
  704.         try:
  705.             data = 'link=%s' %link
  706.                 #print json.dumps({'link':'https://www.deezer.com/track/126884459'})
  707.                 #print type(json.dumps({'link':'https://www.deezer.com/track/126884459'}))
  708.             req = urllib2.Request('https://www.mp3fy.com/music/downloader.php')
  709.             req.add_header("User-Agent", "Mozilla/5.0 (X11; U; Linux i686) Gecko/20071127 Firefox/2.0.0.11")
  710.             response = urllib2.urlopen(req, data)
  711.             #print result)
  712.             retDict = json.loads(response.read())
  713.             try:
  714.                 return retDict['dlink']
  715.             except KeyError:
  716.                 return False
  717.         except ValueError:
  718.             print 'Okay... that\'s weird. Wait a momento...'
  719.  
  720. def load_songs():
  721.     'Generate a list of songs from a playlist csv file.'
  722.  
  723.     while True:
  724.         res = raw_input('Choose a mode - \n'
  725.                         '(0) playlist (Song CSV)\n'
  726.                         '(1) playlist (Deezer link)\n'
  727.                         '(2) title   or\n'
  728.                         '(3) iTunes %i Top Charts ? > ' % config_topsongs_limit)
  729.         try:
  730.             if int(res) in (0, 1, 2, 3):
  731.                 break
  732.             else: continue
  733.         except:
  734.             continue
  735.     if int(res) == 0:
  736.         print '''WARNING!
  737. Using the CSV playlist function can be inaccurate and cumbersome. Also, if you don't have bs4 it will crash.'
  738. Try using the Deezer playlist instead!'''
  739.         try:
  740.             #file_path = 'playlist.csv' # FOR EXPERIMENTAL PURPOSES, PLEASE REMOVE
  741.             file_path = sys.argv[1]   # FOR EXPERIMENTAL PURPOSES, PLEASE UNCOMMENT
  742.         except IndexError:
  743.             print 'You need to choose a .csv file to use. If you don\'t have one, '\
  744.                   'get one from <http://joellehman.com/playlist/>!'
  745.             root = __import__('Tkinter').Tk()
  746.             root.withdraw()
  747.             root.update()
  748.             file_path = openfile(title='Choose a .csv file')
  749.             print file_path
  750.  
  751.         try:                            
  752.             mFile = open(file_path, 'r')
  753.             sPlaylist = csv.reader(mFile)
  754.             return sPlaylist, False
  755.         except IOError:
  756.             return 'NoFile'
  757.     elif int(res) == 1:
  758.         deezerLink = raw_input('What\'s the link/ID to the Deezer playlist? > ')
  759.         deezerAPILink = 'http://api.deezer.com/playlist/%s' %deezerLink.split('/')[-1]
  760.             #print deezerAPILink
  761.             #link_data_json = urllib2.urlopen(deezerAPILink).read()
  762.         try:
  763.             link_data_json = urllib2.urlopen(deezerAPILink).read()
  764.         except:
  765.             print 'Oops! Did something go wrong? Check your link...'
  766.             sys.exit('FAIL')
  767.  
  768.         #print link_data_json
  769.         link_data = json.loads(link_data_json)
  770.         if 'error' in link_data:
  771.             print 'Something went wrong. Check your link...'
  772.             sys.exit('FAIL')
  773.         #print link_data
  774.         #print link_data['tracks']['data']
  775.         sPlaylist = []
  776.         for trackData in link_data['tracks']['data']:
  777.             sPlaylist.append((trackData['title'],
  778.                               trackData['artist']['name'],
  779.                               trackData['link']))
  780.  
  781.         #Print sPlaylist
  782.         return ['JUNK'] + sPlaylist, True
  783.  
  784.     elif int(res) == 2:
  785.         track = raw_input('Song name? > ')
  786.         artist = raw_input('Artist name? > ')
  787.         return deezerSearch(track, artist)
  788.         # return ('JUNK_DISCARD',(song_name, track))
  789.  
  790.     else:
  791.         iTunesText = ('iTunes country code for your country?\n'
  792.                       '(AU) for Australia\n'
  793.                       '(US) for the United States\n'
  794.                       '(GB) for Great Britain) > ')
  795.  
  796.         if sys.version_info[0] == 2:
  797.             countryCode = raw_input(iTunesText)
  798.         else:
  799.             countryCode =     input(iTunesText)
  800.  
  801.         countryCode = countryCode.lower()
  802.         feedlink = "http://itunes.apple.com/%s/rss/topsongs/limit=%i/explicit=true/xml" % \
  803.             (countryCode, config_topsongs_limit)
  804.         feed = feedparser.parse(feedlink)
  805.         if feed["bozo"]:
  806.             input("Not a valid country code.")
  807.             sys.exit()
  808.  
  809.         songslist = []
  810.         for item in feed["items"]:
  811.             title = unicodedata.normalize('NFKD', u"%s" %(item["title"])) \
  812.                 .encode('ascii', 'ignore')
  813.             #print title                   
  814.             title  = "".join ( title         .split(" - ")[:-1] )
  815.             artist =           item["title"] .split(" - ")[ -1]
  816.  
  817. #I Feel It Coming (feat. Daft Punk) - The Weeknd
  818. #I Feel It Coming
  819.  
  820. #I Dont Wanna Live Forever (Fifty Shades Darker) - ZAYN & Taylor Swift
  821. #I Dont Wanna Live Forever (Fi
  822. #I Don\u2019t Wann...
  823.             phrases = ("feat\.", "ft\.")
  824.             modifiers = (
  825.                 (r"\("  , r"\)" ),
  826.                 ( ""    ,  ""   ),
  827.                 ( " "   ,  ""   ),
  828.                 ( " "   ,  " "  )
  829.             )
  830.             for ph in phrases:
  831.                 for mod in modifiers:
  832.                     title = re.sub( mod[0] + ph + ".+" + mod[1], "", title)
  833.  
  834.             print title
  835.             title = title.strip()
  836.  
  837.             songslist.append( [title, artist] )
  838.         return ["JUNK"] + songslist, False
  839.  
  840. def download_songs(sPlaylist, url=False):
  841.     'Download songs, using the Deezer search engine.'
  842.     global completedSongs, totalSongs, act_threads
  843.  
  844.     currentSong = 1
  845.     green = Fore.GREEN
  846.     black = Back.BLACK
  847.     userOpin = raw_input('Do you want to number the songs? [Y/N]')
  848.     userOpin = userOpin.lower().startswith('y')
  849.     print Fore.RED + "Downloading to '" + config_DL_Dir + "'"
  850.     print green + 'Starting download with Deezer'
  851.     first = True
  852.     second = False
  853.     completedSongs = 0
  854.     totalSongs = len(sPlaylist)-1
  855.     startProgress('  DL (total) > ')
  856.     unchUrl = copy.deepcopy(url)
  857.         #progress(file_size_dl * 100. / file_size_int, '  DL (total) > ')
  858.         #endProgress('  DL (done!)> ')
  859.     for song in sPlaylist:
  860.         while act_threads > 4:
  861.             time.sleep(0.5)
  862.         if first:
  863.             first = False
  864.             second = True
  865.             continue
  866.         if second:
  867.             second = False
  868.  
  869.         print green + '    Song: %s' %song[0] + ' '*(59-len('    Song: %s' %song[0]))
  870.         progress( completedSongs * 100. / totalSongs, '  DL (total) > ')
  871.  
  872.         if userOpin:
  873.             sTitle = '%s. %s' %(currentSong, song[0])
  874.         else:
  875.             sTitle = song[0]
  876.  
  877.         file_name = 'downloads/%s - %s.mp3' %(
  878.             sTitle.rstrip(),
  879.             song[1].rstrip()
  880.         )
  881.  
  882.         file_name = file_name.rstrip()
  883.         if os.path.exists(file_name):
  884.             print '      Exists already. Skipping...' + ' '*(59-\
  885.                                                              len  ('      Exists already. Skipping...'))
  886.             progress(completedSongs * 100. / totalSongs, '  DL (total) > ')
  887.             currentSong += 1
  888.             continue
  889.  
  890.         if not unchUrl:
  891.  
  892.             songTitle  = song[0]
  893.             artistName = song[1]
  894.  
  895.             url = 'http://api.deezer.com/search?q=%s%%20%s' %(
  896.                 MakeUrlItem( songTitle),
  897.                 MakeUrlItem(artistName) )
  898.  
  899.             html_mpfy = urllib2.urlopen( url ).read()
  900.             js = json.loads(html_mpfy)
  901.             try:
  902.                 downloadLink = js["data"][0]["id"]
  903.                 #scriptDownload(downloadId, file_name)
  904.                 if userOpin:
  905.                     dw_thr = threading.Thread(target=scriptDownload, args=(downloadLink, file_name))
  906.                 else:
  907.                     dw_thr = threading.Thread(target=scriptDownload, args=(downloadLink, ))
  908.                 dw_thr.start()
  909.  
  910.             except IndexError:
  911.                 print url      
  912.  
  913.                 print green + '      Song not found. Skipping...' + ' '*(59-len('      Song not found. Skipping...'))
  914.                 progress(completedSongs * 100. / totalSongs, '  DL (total) > ')
  915.         else:
  916.             try:
  917.                 link = re.findall(r"track/(\d*)", song[2])[0]
  918.             except IndexError:
  919.                 link = ""
  920.             if link:
  921.                 dw_thr = threading.Thread(target=scriptDownload, args=(link, ))
  922.                 #download_file(link, sTitle, song[1])
  923.                 dw_thr.start()
  924.             else:
  925.                 print '    Skipping... (Could not download)' + \
  926.                       ' '*(59-len('    Skipping... (Could not download)'))
  927.                 progress(completedSongs * 100. / totalSongs, '  DL (total) > ')
  928.         currentSong += 1
  929.         progress(completedSongs * 100. / totalSongs, '  DL (total) > ')
  930.     while True:
  931.         if act_threads == 1:
  932.             break
  933.         time.sleep(0.5)
  934.     endProgress('  DL (done!)> ')
  935.  
  936. def startProgress(title):
  937.     to_write = title + ": [" + "-"*40 + "]"
  938.     sys.stdout.write(to_write + chr(8)*len(to_write))
  939.     sys.stdout.flush()
  940.  
  941. def progress(x, title):
  942.     x_t = int(x * 40 // 100)
  943.     to_write = title + ": [" + "#"*x_t + "-"*(40-x_t) + "]"
  944.         #sys.stdout.write("#" * (x - progress_x))
  945.     sys.stdout.write(to_write + chr(8)*len(to_write))
  946.     sys.stdout.flush()
  947.  
  948. def endProgress(title):
  949.     to_write = title + ": [" + "#"*40 + "]\n"
  950.     sys.stdout.write(to_write)
  951.     sys.stdout.flush()
  952.  
  953. def MakeUrlItem(item):
  954.     try:
  955.         return '+'.join(urllib.quote_plus(item).split(' '))
  956.     except:
  957.         return ''
  958.  
  959. def deezerSearch(title, artist=''):
  960.     'Searches for a song using the Deezer API.'
  961.  
  962.     query = 'http://api.deezer.com/search?q=%s%s%s' %  \
  963.         ( MakeUrlItem( title) , \
  964.           r'%20', \
  965.           MakeUrlItem( artist ) \
  966.           )
  967.     #print query
  968.     qResultRaw = urllib2.urlopen(
  969.         query).read()
  970.     cnt = 1
  971.  
  972.     #print qResultRaw
  973.     try:
  974.         qResult = json.loads(qResultRaw)['data']
  975.     except ValueError:
  976.         print 'Things went wrong. There was NOTHING returned for your search! :O'
  977.         return None
  978.  
  979.     cnt = 0
  980.     group = 0
  981.     print Fore.RESET + 'Press the KEY to select, or any other to continue'
  982.     print Fore.RED + Back.WHITE + 'KEY:\tTRACK - ARTIST'
  983.     for i in qResult:
  984.         try:
  985.             if group and group*5 + cnt >= len(qResult):
  986.                 print Fore.RESET + 'Looks like the end! Sorry...'
  987.             if not group:
  988.                 print Fore.RED + Back.WHITE + '%s:\t%s - %s' %(cnt+1, qResult[cnt]['title'], qResult[cnt]['artist']['name'])
  989.             else:
  990.                 print Fore.RED + Back.WHITE + '%s:\t%s - %s' %(cnt+1, qResult[(group*5-1) + cnt]['title'], qResult[(group*5-1) + cnt]['artist']['name'])
  991.         except:
  992.             print Fore.RESET + 'Error'
  993.  
  994.         if cnt >= 4 or group*5 + cnt + 1 == len(qResult): # NEED TO FINISH
  995.             #print range(1, cnt+2)
  996.             q = raw_input(Fore.RESET + Back.RESET + '  Which song? ')
  997.             if q in [str(x) for x in range(1, cnt+2)]:
  998.                 cnNum = int(q)-1
  999.                 if not group:
  1000.                     qr = qResult[cnNum]
  1001.                     #userOpin = raw_input(Fore.RESET + '  Chosen: %s by %s. Confirm? ' %(qr['title'], qResult[cnNum]['artist']['name']))
  1002.                     if True: #userOpin.lower().startswith('y'):
  1003.                         result = previewSong(qr)
  1004.                         if result:
  1005.                             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.)"
  1006.                             return ['JUNK',[qr['title'],
  1007.                                             qr['artist']['name'],
  1008.                                             qr['link']]], True
  1009.                 else:
  1010.                     qr = qResult[(group*5-1)+cnNum]
  1011.                     #userOpin = raw_input('  Chosen: %s by %s. Confirm? ' %(\
  1012.                     #    qr['title'],
  1013.                     #    qr['artist']['name']))
  1014.                     if True:#userOpin.lower().startswith('y'):
  1015.                         result = previewSong(qr)
  1016.                         if result:
  1017.                             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.)"
  1018.                             return ['JUNK',[qr['title'],
  1019.                                             qr['artist']['name'],
  1020.                                             qr['link']]], True
  1021.             print Fore.RESET + 'Press the KEY to select, or any other to continue'
  1022.             print Fore.RED + 'KEY:\tTRACK - ARTIST'
  1023.             group += 1
  1024.             cnt = -1
  1025.         cnt += 1
  1026.     print 'Looks like the end...'
  1027.     sys.exit('FAIL')
  1028.  
  1029. def previewSong(qPlaylist):
  1030.     'Creates a preview of the song'
  1031.  
  1032.     urllib.urlretrieve(qPlaylist['preview'], 'temp.mp3')
  1033.     #with open('temp.mp3','w') as tempMedia:
  1034.         #content = urllib2.urlopen(qPlaylist['preview']).read()
  1035.         #print qPlaylist['preview']
  1036.         #tempMedia.write(content)
  1037.  
  1038.     player = pyglet.media.Player()
  1039.     player.queue(pyglet.media.load('temp.mp3'))
  1040.     player.play()
  1041.     userOpin = raw_input('  Are you sure you want to choose this song? ')
  1042.     player.pause()
  1043.     del player
  1044.     if userOpin.lower().startswith('y'):
  1045.         return True
  1046.  
  1047. def mainExC():
  1048.     'Run the program as an executable'
  1049.     global act_threads
  1050.     sPlaylist, oldSearch = load_songs()
  1051.     if sPlaylist:
  1052.         download_songs(sPlaylist, oldSearch)
  1053.         return 'SUCCESS'
  1054.     else:
  1055.         return 'FAIL'
  1056.  
  1057. act_threads = 1
  1058. completedSongs = 0
  1059. totalSongs = 0
  1060.  
  1061.  
  1062.  
  1063. if __name__ == '__main__':
  1064.     sys.exit( main() )
Add Comment
Please, Sign In to add comment