Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
02f022d
Re worked.
May 2, 2015
99927f5
Added directory options.
May 16, 2015
c835a87
Added commandline arguments
May 17, 2015
e4c2211
Included tagging. Improved the code a bit.
May 30, 2015
64cc027
Added Tagging. Improved the code a bit.
May 30, 2015
067adf9
Fixed an issue.
May 30, 2015
0da4a00
Update README.md
May 30, 2015
8eb95ec
Update README.md
May 30, 2015
b04c484
Update README.md
May 31, 2015
76c67b7
Fixed tagging error.
Jun 3, 2015
cf0ba98
Fixed minor errors.
Jun 3, 2015
7bc85d2
Minor fixes
Jun 6, 2015
81ecc68
Update README.md
Jun 7, 2015
6891aa3
Added sessions. Fixed folder name errors.
Jun 12, 2015
99fd4d1
Merge branch 'master' of https://github.com/Suyash458/bandcamp-dl
Jun 12, 2015
7fe6568
Added argparse.
Jul 6, 2015
d5c2ad6
Improved exception handling.
Jul 14, 2015
f9c4933
Removed stray folder
Jul 14, 2015
7a236f3
Fixed filename error.
Jul 17, 2015
465fd92
Changed getFile function
Jul 17, 2015
7ef10d0
Improved exception handling.
Jul 28, 2015
aec869d
Fixed single track error.
Aug 3, 2015
b0fff00
Fixed import error.
Aug 3, 2015
0693910
Update README.md
Aug 8, 2015
eac49b6
Update README.md
Aug 8, 2015
72885be
Edited readme.md
Aug 17, 2015
57de2a1
Changed bandcamp-dl.py
Aug 17, 2015
661d7f1
changed downloader.py
Aug 17, 2015
4258893
Fixed broken regex. Improved pogressbar.
Sep 10, 2015
7941c7d
Removed unnecessary folder
Sep 10, 2015
746c851
Fixed regex and album art bug
Jan 7, 2016
571193e
removed url printing
Jan 17, 2016
1aa8dd2
removed stray file
Jan 17, 2016
b80a432
Fix regex
Feb 6, 2016
8a08785
Fixed non-downloadable track errors
May 14, 2016
e4e37ed
Fix regex for single track page
Jul 29, 2016
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
bdl/*
songs/*
*.pyc
30 changes: 27 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,33 @@
A small command-line program to download tracks from Bandcamp.com

==================================================================
##Installation

####From Source
* Clone the repo or download the zip
* Make sure you have pip installed
* `cd` to the folder
* `pip -install -r "requirements.txt"`

##Usage
* Download the zip and extract the files
* Create a folder `songs` at the root of your folder
* On the terminal or Command Prompt Type
`python bandcamp.py <url> `
`python bandcamp-dl.py --url "url" --dir "directory"`
* The current working directory is the default download location.
* Adding the --include option overrides the --exclude option.
* Example : `python bandcamp-dl.py --url "URL" --dir "D:\Music"`
* Example : `python bandcamp-dl.py --url "URL" --dir "D:\Music" --exclude 1 2 3`

##Options
-h, --help show this help message and exit
--url URL URL to download tracks from.
--dir DIR Directory to save tracks in. Default value is the current
working directory.
--exclude exclude a list of tracks.(List of space separeted integers)
--include specifically download a list of tracks.(List of space separated integers)
--limit limits the number of tracks to be downloaded.(Single integer)
--range Range of track numbers to download.(Two space separated integers)

###Dependencies
* BeautifulSoup - HTML parsing
* Requests - for retrieving HTML
* Mutagen - To tag mp3 files and add album art
22 changes: 22 additions & 0 deletions bandcamp-dl.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from downloader import downloader
import sys,argparse,os

parser = argparse.ArgumentParser()
parser.add_argument('--url',default = None, type = str, help = 'URL to download tracks from.')
parser.add_argument('--dir',default = os.getcwd(), type = str, help = 'Directory to save tracks in. Default value is the current working directory.')
parser.add_argument('--exclude',nargs = '+',type = int, help = 'Enter track numbers to exclude.')
parser.add_argument('--include',nargs = '+',type = int, help = 'Enter track numbers to include.')
parser.add_argument('--limit',default = None,type = int,help = 'Maximum number of tracks to download.')
parser.add_argument('--range',nargs = 2,type = int, help = 'Enter range of tracks to download.')

if __name__ == '__main__':
args = parser.parse_args()
if args.include is not None:
args.include = set(args.include)
if args.exclude is not None:
args.exclude = set(args.exclude)
if args.url == None:
print "No URL entered."
sys.exit(0)
downloader = downloader.Downloader(args)
downloader.Download()
48 changes: 0 additions & 48 deletions bandcamp.py

This file was deleted.

Empty file added downloader/__init__.py
Empty file.
217 changes: 217 additions & 0 deletions downloader/downloader.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
from __future__ import print_function
import re,json,requests
from bs4 import BeautifulSoup
import sys,os
from mutagen.mp3 import MP3
from mutagen.id3 import ID3, TIT2, TALB, TPE1, TPE2, COMM, USLT, TCOM, TCON, TDRC,APIC
from contextlib import closing
from time import sleep
import socket


class Downloader():

def __init__(self,args):
self.url = args.url
self.dirname = args.dir
self.session = requests.Session()
self.session.mount("http://", requests.adapters.HTTPAdapter(max_retries=2))
self.session.mount("https://", requests.adapters.HTTPAdapter(max_retries=2))
self.args = args
self.completed = 0

def connectionHandler(self,url,stream = False,timeout = 15):
try:
response = self.session.get(url,stream = stream,timeout = timeout)
assert response.status_code == 200
return response
except (requests.exceptions.ConnectionError,
TypeError,
socket.error):
print("Connection error. Retrying in 15 seconds.")
sleep(15)
return self.connectionHandler(url,stream)
except (AssertionError,requests.exceptions.HTTPError):
print("Connection error or invalid URL.")
sys.exit(0)
except KeyboardInterrupt:
print ("\nExiting.")
sys.exit(0)

def getData(self,soup):
content = soup.find('meta',{'name':'Description'})['content']
JSdata = soup.findAll('script')
var = re.search('trackinfo : .*?}]',str(JSdata))
if not var:
var = re.search('trackinfo: .*?}]',str(JSdata))
var = var.group()[11::]
tracks = json.loads(var)
artist = re.search('artist: [^,]*',str(JSdata)).group()[8::].replace('"','')
album = re.search('album_title: [^,]*"',str(JSdata))
album = album.group().split(': ')[1][1:-1] if album is not None else None
year = content.strip()[-1:-5:-1][::-1]
metadata = {'artist' : artist,
'album' : album,
'year' : year
}
return metadata,tracks

def getAlbumArt(self,soup):
JSdata = soup.findAll('script')
albumArtURL = re.search('artFullsizeUrl.*",',str(JSdata)).group()
albumArtURL = re.search('artFullsizeUrl.*",', albumArtURL[0:100]).group()[17:-2]
print ("Downloading Album Art.")
format = albumArtURL.split('.')[-1]
self.getFile('album-art.' + format, albumArtURL, True)

def progressBar(self,done,file_size):
percentage = ((done/file_size)*100)
temp = '#'*int((percentage/5))
progressString = '\r[{0: <20}] | {1:2.2f} %'.format(temp, percentage)
print(progressString,end ='')
sys.stdout.flush()

def getFile(self,filename,link,silent = False):
new_filename = re.sub('[\/:*"?<>|]','_',filename)
if link is not None:
if silent:
try:
with closing(self.connectionHandler(link,True,5)) as response:
with open(new_filename,'wb') as file:
for chunk in response.iter_content(chunk_size=1024):
if chunk:
file.write(chunk)
file.flush()
return new_filename
except KeyboardInterrupt:
print ("\nExiting.")
sys.exit(0)
except socket.error:
return self.getFile(filename,link,silent)
except requests.exceptions.ConnectionError:
return self.getFile(filename,link,silent)
print ("\nConnecting to stream...")
try:
with closing(self.connectionHandler(link,True,5)) as response:
print ("Response: "+ str(response.status_code))
file_size = float(response.headers['content-length'])
if(os.path.isfile(new_filename)):
if os.path.getsize(new_filename) >= long(file_size):
print (new_filename + " already exists, skipping.")
return new_filename
else:
print ("Incomplete download, restarting.")
print ("File Size: " + '%.2f' % (file_size/(1000**2)) + ' MB')
print ("Saving as: " + new_filename)
done = 0
try:
with open(new_filename,'wb') as file:
for chunk in response.iter_content(chunk_size=1024):
if chunk:
file.write(chunk)
file.flush()
done += len(chunk)
self.progressBar(done,file_size)

if os.path.getsize(new_filename) < long(file_size):
print( "\nConnection error. Restarting in 15 seconds.")
sleep(15)
return self.getFile(filename,link,silent)
print ("\nDownload complete.")
return new_filename
except KeyboardInterrupt:
print ("\nExiting.")
sys.exit(0)
except (socket.error,
requests.exceptions.ConnectionError):
return self.getFile(filename,link,silent)
except KeyboardInterrupt:
print ("\nExiting." )
sys.exit(0)
else:
return

def tagFile(self,filename,metadata,track):
audio = MP3(filename,ID3=ID3)
try:
audio.add_tags()
except:
return
with open('album-art.jpg','rb') as file:
image = file.read()
audio.tags.add(
APIC(
encoding=3,
mime='image/jpeg',
type=3,
desc=u'Cover',
data=image
)
)
audio.tags["TIT2"] = TIT2(encoding=3, text=track['title'])
audio.tags["TALB"] = TALB(encoding=3, text=metadata['album'])
audio.tags["TPE1"] = TPE1(encoding=3, text=metadata['artist'])
audio.tags["TDRC"] = TDRC(encoding=3, text=unicode(metadata['year']))
audio.save()

def getAlbum(self,tracks,metadata):
for index, track in enumerate(tracks):
if track['file'] is None:
continue
if track['track_num'] is not None:
filename = str(track['track_num']) + '. ' + str(track['title'].encode('utf-8')) + '.mp3'
else:
filename = str(track['title'].encode('utf-8') + '.mp3')
link = 'https:' + track['file']['mp3-128']
if self.args.limit is not None:
if self.completed == self.args.limit:
return
if self.args.include is not None:
if self.completed == len(self.args.include):
break
if(index + 1) not in self.args.include:
if self.args.range:
if not (self.args.range[0] <= (index + 1) <= self.args.range[1]):
continue
else:
continue
elif self.args.exclude is not None:
if (index + 1) in self.args.exclude:
print ("Skipping " + str(track['title'].encode('utf-8')))
continue
new_filename = self.getFile(filename,link)
self.tagFile(new_filename,metadata,track)

def Download(self):
if self.url is None:
print ("No URL entered.")
return
try:
if self.dirname is not None:
os.chdir(str(self.dirname))
print ("Connecting ... ")
response = self.connectionHandler(self.url)
except WindowsError:
print ("Invalid Directory")
return
except requests.exceptions:
print ("Network Error")
return
print ("Response: " + str(response.status_code))
assert response.status_code == 200
soup = BeautifulSoup(response.text,'html.parser')
metadata,tracks = self.getData(soup)
if metadata['album'] is not None:
folder = re.sub('[\/:*"?<>|]','_',metadata['artist'] + ' - ' + metadata['album'])
else:
folder = re.sub('[\/:*"?<>|]','_',metadata['artist'])
if not os.path.isdir(folder):
os.mkdir(folder)
os.chdir(os.getcwd() + '\\' + str(folder))
self.getAlbumArt(soup)
print ("Saving in : " + os.getcwd())
print (str(len(tracks)) + " track(s) found.")
if metadata['album'] is not None:
print ("Album : " + metadata['album'])
print ("Artist: " + metadata['artist'])
self.getAlbum(tracks,metadata)
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
beautifulsoup4==4.3.2
requests==2.6.2
wsgiref==0.1.2
mutagen==1.29