Added debug option, corrected an argument check
`--debug` option is now in, should give you a better idea of what is happening when used and where a possible error may be.master
parent
c6ae5a99fc
commit
c460cdc09c
|
@ -9,6 +9,7 @@ Arguments:
|
|||
Options:
|
||||
-h --help Show this screen.
|
||||
-v --version Show version.
|
||||
-d --debug Verbose logging.
|
||||
--artist=<artist> The artist's slug (from the URL)
|
||||
--track=<track> The track's slug (from the URL)
|
||||
--album=<album> The album's slug (from the URL)
|
||||
|
@ -17,9 +18,9 @@ Options:
|
|||
--base-dir=<dir> Base location of which all files are downloaded.
|
||||
-f --full-album Download only if all tracks are available.
|
||||
-o --overwrite Overwrite tracks that already exist. Default is False.
|
||||
-n --no-art Skip grabbing album art
|
||||
-n --no-art Skip grabbing album art.
|
||||
-e --embed-lyrics Embed track lyrics (If available)
|
||||
-g --group Use album/track Label as iTunes grouping
|
||||
-g --group Use album/track Label as iTunes grouping.
|
||||
-r --embed-art Embed album art (If available)
|
||||
-y --no-slugify Disable slugification of track, album, and artist names.
|
||||
"""
|
||||
|
@ -46,6 +47,7 @@ Iheanyi:
|
|||
|
||||
import os
|
||||
import ast
|
||||
import logging
|
||||
|
||||
from docopt import docopt
|
||||
|
||||
|
@ -57,6 +59,9 @@ from bandcamp_dl.__init__ import __version__
|
|||
def main():
|
||||
arguments = docopt(__doc__, version='bandcamp-dl {}'.format(__version__))
|
||||
|
||||
if arguments['--debug']:
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
|
||||
bandcamp = Bandcamp()
|
||||
|
||||
basedir = arguments['--base-dir'] or os.getcwd()
|
||||
|
@ -78,18 +83,27 @@ def main():
|
|||
else:
|
||||
url = arguments['URL']
|
||||
|
||||
logging.debug("\n\tURL: {}".format(url))
|
||||
|
||||
if arguments['--no-art']:
|
||||
album = bandcamp.parse(url, False, arguments['--embed-lyrics'])
|
||||
album = bandcamp.parse(url, False, arguments['--embed-lyrics'], arguments['--debug'])
|
||||
else:
|
||||
album = bandcamp.parse(url, True, arguments['--embed-lyrics'])
|
||||
album = bandcamp.parse(url, True, arguments['--embed-lyrics'], arguments['--debug'])
|
||||
|
||||
logging.debug(" Album data:\n\t{}".format(album))
|
||||
|
||||
if arguments['--full-album'] and not album['full']:
|
||||
print("Full album not available. Skipping...")
|
||||
elif arguments['URL']:
|
||||
elif arguments['URL'] or arguments['--artist']:
|
||||
logging.debug("Preparing download process..")
|
||||
bandcamp_downloader = BandcampDownloader(arguments['--template'], basedir, arguments['--overwrite'],
|
||||
arguments['--embed-lyrics'], arguments['--group'],
|
||||
arguments['--embed-art'], arguments['--no-slugify'], url)
|
||||
arguments['--embed-art'], arguments['--no-slugify'],
|
||||
arguments['--debug'], url)
|
||||
logging.debug("Initiating download process..")
|
||||
bandcamp_downloader.start(album)
|
||||
else:
|
||||
logging.debug(" /!\ Something went horribly wrong /!\ ")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
from datetime import datetime as dt
|
||||
import json
|
||||
import logging
|
||||
|
||||
import requests
|
||||
from bs4 import BeautifulSoup
|
||||
|
@ -13,14 +14,18 @@ class Bandcamp:
|
|||
def __init__(self):
|
||||
self.headers = {'User-Agent': 'bandcamp-dl/{} (https://github.com/iheanyi/bandcamp-dl)'.format(__version__)}
|
||||
|
||||
def parse(self, url: str, art: bool=True, lyrics: bool=False) -> dict or None:
|
||||
def parse(self, url: str, art: bool=True, lyrics: bool=False, debugging: bool=False) -> dict or None:
|
||||
"""Requests the page, cherry picks album info
|
||||
|
||||
:param url: album/track url
|
||||
:param art: if True download album art
|
||||
:param lyrics: if True fetch track lyrics
|
||||
:param debugging: if True then verbose output
|
||||
:return: album metadata
|
||||
"""
|
||||
if debugging:
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
|
||||
try:
|
||||
response = requests.get(url, headers=self.headers)
|
||||
except requests.exceptions.MissingSchema:
|
||||
|
@ -31,11 +36,14 @@ class Bandcamp:
|
|||
except FeatureNotFound:
|
||||
self.soup = BeautifulSoup(response.text, "html.parser")
|
||||
|
||||
bandcamp_json = BandcampJSON(self.soup).generate()
|
||||
logging.debug(" Generating BandcampJSON..")
|
||||
bandcamp_json = BandcampJSON(self.soup, debugging).generate()
|
||||
album_json = json.loads(bandcamp_json[0])
|
||||
embed_json = json.loads(bandcamp_json[1])
|
||||
page_json = json.loads(bandcamp_json[2])
|
||||
logging.debug(" BandcampJSON generated..")
|
||||
|
||||
logging.debug(" Generating Album..")
|
||||
self.tracks = album_json['trackinfo']
|
||||
|
||||
album_release = album_json['album_release_date']
|
||||
|
@ -74,9 +82,11 @@ class Bandcamp:
|
|||
if art:
|
||||
album['art'] = self.get_album_art()
|
||||
|
||||
logging.debug(" Album generated..")
|
||||
return album
|
||||
|
||||
def get_track_lyrics(self, track_url):
|
||||
logging.debug(" Fetching track lyrics..")
|
||||
track_page = requests.get(track_url, headers=self.headers)
|
||||
try:
|
||||
track_soup = BeautifulSoup(track_page.text, "lxml")
|
||||
|
@ -84,8 +94,10 @@ class Bandcamp:
|
|||
track_soup = BeautifulSoup(track_page.text, "html.parser")
|
||||
track_lyrics = track_soup.find("div", {"class": "lyricsText"})
|
||||
if track_lyrics:
|
||||
logging.debug(" Lyrics retrieved..")
|
||||
return track_lyrics.text
|
||||
else:
|
||||
logging.debug(" Lyrics not found..")
|
||||
return "lyrics unavailable"
|
||||
|
||||
def all_tracks_available(self) -> bool:
|
||||
|
@ -105,6 +117,7 @@ class Bandcamp:
|
|||
:param track: track dict
|
||||
:return: track metadata dict
|
||||
"""
|
||||
logging.debug(" Generating track metadata..")
|
||||
track_metadata = {
|
||||
"duration": track['duration'],
|
||||
"track": str(track['track_num']),
|
||||
|
@ -122,6 +135,7 @@ class Bandcamp:
|
|||
track['lyrics'] = "lyrics unavailable"
|
||||
track_metadata['lyrics'] = track['lyrics'].replace('\\r\\n', '\n')
|
||||
|
||||
logging.debug(" Track metadata generated..")
|
||||
return track_metadata
|
||||
|
||||
@staticmethod
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import os
|
||||
import sys
|
||||
import logging
|
||||
|
||||
import requests
|
||||
from mutagen.mp3 import MP3, EasyMP3
|
||||
|
@ -17,7 +18,7 @@ from bandcamp_dl.__init__ import __version__
|
|||
|
||||
|
||||
class BandcampDownloader:
|
||||
def __init__(self, template, directory, overwrite, embed_lyrics, grouping, embed_art, no_slugify, urls=None):
|
||||
def __init__(self, template, directory, overwrite, embed_lyrics, grouping, embed_art, no_slugify, debugging, urls=None):
|
||||
"""Initialize variables we will need throughout the Class
|
||||
|
||||
:param urls: list of urls
|
||||
|
@ -39,12 +40,16 @@ class BandcampDownloader:
|
|||
self.embed_art = embed_art
|
||||
self.embed_lyrics = embed_lyrics
|
||||
self.no_slugify = no_slugify
|
||||
self.debugging = debugging
|
||||
|
||||
def start(self, album: dict):
|
||||
"""Start album download process
|
||||
|
||||
:param album: album dict
|
||||
"""
|
||||
if self.debugging:
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
|
||||
if album['full'] is not True:
|
||||
choice = input("Track list incomplete, some tracks may be private, download anyway? (yes/no): ").lower()
|
||||
if choice == "yes" or choice == "y":
|
||||
|
@ -62,6 +67,7 @@ class BandcampDownloader:
|
|||
:param track: track metadata
|
||||
:return: filepath
|
||||
"""
|
||||
logging.debug(" Generating filepath/trackname..")
|
||||
path = self.template
|
||||
|
||||
if self.no_slugify:
|
||||
|
@ -80,6 +86,8 @@ class BandcampDownloader:
|
|||
|
||||
path = u"{0}/{1}.{2}".format(self.directory, path, "mp3")
|
||||
|
||||
logging.debug(" filepath/trackname generated..")
|
||||
logging.debug("\n\tPath: {}".format(path))
|
||||
return path
|
||||
|
||||
@staticmethod
|
||||
|
@ -90,6 +98,8 @@ class BandcampDownloader:
|
|||
:return: directory path
|
||||
"""
|
||||
directory = os.path.dirname(filename)
|
||||
logging.debug(" Directory:\n\t{}".format(directory))
|
||||
logging.debug(" Directory doesn't exist, creating..")
|
||||
if not os.path.exists(directory):
|
||||
os.makedirs(directory)
|
||||
|
||||
|
@ -120,6 +130,8 @@ class BandcampDownloader:
|
|||
filename = filepath.rsplit('/', 1)[1]
|
||||
dirname = self.create_directory(filepath)
|
||||
|
||||
logging.debug(" Current file:\n\t{}".format(filepath))
|
||||
|
||||
if album['art'] and not os.path.exists(dirname + "/cover.jpg"):
|
||||
try:
|
||||
with open(dirname + "/cover.jpg", "wb") as f:
|
||||
|
@ -161,11 +173,12 @@ class BandcampDownloader:
|
|||
for data in r.iter_content(chunk_size=total):
|
||||
dl += len(data)
|
||||
f.write(data)
|
||||
done = int(50 * dl / file_length)
|
||||
sys.stdout.write(
|
||||
"\r({}/{}) [{}{}] :: Downloading: {}".format(self.track_num, self.num_tracks,
|
||||
"=" * done, " " * (50 - done),
|
||||
filename[:-8]))
|
||||
if not self.debugging:
|
||||
done = int(50 * dl / file_length)
|
||||
sys.stdout.write(
|
||||
"\r({}/{}) [{}{}] :: Downloading: {}".format(self.track_num, self.num_tracks,
|
||||
"=" * done, " " * (50 - done),
|
||||
filename[:-8]))
|
||||
sys.stdout.flush()
|
||||
local_size = os.path.getsize(filepath)
|
||||
# if the local filesize before encoding doesn't match the remote filesize redownload
|
||||
|
@ -203,10 +216,13 @@ class BandcampDownloader:
|
|||
:param filepath: name of mp3 file
|
||||
:param meta: dict of track metadata
|
||||
"""
|
||||
logging.debug(" Encoding process starting..")
|
||||
|
||||
filename = filepath.rsplit('/', 1)[1][:-8]
|
||||
|
||||
sys.stdout.flush()
|
||||
sys.stdout.write("\r({}/{}) [{}] :: Encoding: {}".format(self.track_num, self.num_tracks, "=" * 50, filename))
|
||||
if not self.debugging:
|
||||
sys.stdout.flush()
|
||||
sys.stdout.write("\r({}/{}) [{}] :: Encoding: {}".format(self.track_num, self.num_tracks, "=" * 50, filename))
|
||||
|
||||
audio = MP3(filepath)
|
||||
audio.delete()
|
||||
|
@ -234,10 +250,14 @@ class BandcampDownloader:
|
|||
audio["date"] = meta["date"]
|
||||
audio.save()
|
||||
|
||||
logging.debug(" Encoding process finished..")
|
||||
logging.debug(" Renaming:\n\t{} -to-> {}".format(filepath, filepath[:-4]))
|
||||
|
||||
try:
|
||||
os.rename(filepath, filepath[:-4])
|
||||
except WindowsError:
|
||||
os.remove(filepath[:-4])
|
||||
os.rename(filepath, filepath[:-4])
|
||||
|
||||
sys.stdout.write("\r({}/{}) [{}] :: Finished: {}".format(self.track_num, self.num_tracks, "=" * 50, filename))
|
||||
if not self.debugging:
|
||||
sys.stdout.write("\r({}/{}) [{}] :: Finished: {}".format(self.track_num, self.num_tracks, "=" * 50, filename))
|
||||
|
|
|
@ -1,20 +1,25 @@
|
|||
import re
|
||||
import logging
|
||||
|
||||
import demjson
|
||||
|
||||
|
||||
class BandcampJSON:
|
||||
def __init__(self, body):
|
||||
def __init__(self, body, debugging: bool=False):
|
||||
self.body = body
|
||||
self.targets = ['TralbumData', 'EmbedData', 'pagedata']
|
||||
self.json_data = []
|
||||
|
||||
if debugging:
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
|
||||
def generate(self) -> list:
|
||||
"""Iterate through targets grabbing needed data"""
|
||||
for target in self.targets:
|
||||
if target[:4] == 'page':
|
||||
self.get_pagedata()
|
||||
else:
|
||||
logging.debug(" Grabbing target data..")
|
||||
self.regex = re.compile(r"(?<=var\s" + target + "\s=\s).*?(?=};)", re.DOTALL)
|
||||
self.target = target
|
||||
self.js_to_json()
|
||||
|
@ -28,6 +33,7 @@ class BandcampJSON:
|
|||
|
||||
def get_js(self):
|
||||
"""Get <script> element containing the data we need and return the raw JS"""
|
||||
logging.debug(" Grabbing embedded script..")
|
||||
self.js_data = self.body.find("script", {"src": False}, text=re.compile(self.target)).string
|
||||
self.extract_data(self.js_data)
|
||||
|
||||
|
@ -40,6 +46,7 @@ class BandcampJSON:
|
|||
|
||||
def js_to_json(self):
|
||||
"""Convert JavaScript dictionary to JSON"""
|
||||
logging.debug(" Converting JS to JSON..")
|
||||
self.get_js()
|
||||
# Decode with demjson first to reformat keys and lists
|
||||
decoded_js = demjson.decode(self.js_data)
|
||||
|
|
Loading…
Reference in New Issue