0.0.8 Update
Added --track for downloading individual tracks/singles Added --embed-art to forcibly embed album art if availablemaster
parent
9db2aba929
commit
12327e119b
|
@ -17,6 +17,7 @@ Version 0.0.6
|
|||
- [Enhancement] Individual track downloads work now.
|
||||
- [Bugfix] Fixed imports, now working when installed via pip.
|
||||
- [Note] Last version to officially support Python 2.7.x
|
||||
- [Bugfix] Fixed an encoding issue with accented characters in the filepath. (Thanks `oaubert <https://github.com/oaubert>`_)
|
||||
|
||||
Version 0.0.7
|
||||
-------------
|
||||
|
@ -28,3 +29,11 @@ Version 0.0.7
|
|||
- [Dependency] Slimit is no longer required.
|
||||
- [Dependency] Ply is no longer required.
|
||||
- [Dependency] demjson is now required.
|
||||
- [Bugfix] Downloading singles is now fixed.
|
||||
- [Bugfix] Monkey-patched Requests to fix compatability with Python versions before 3.6.
|
||||
- [Enhancement] Added a --group option to insert a group tag (iTunes)
|
||||
|
||||
Version 0.0.8
|
||||
-------------
|
||||
- [Enhancement] --embed-art option to forcibly embed album art (if available)
|
||||
- [Enhancement] --track option for downloading individual tracks and singles.
|
||||
|
|
80
README.rst
80
README.rst
|
@ -21,7 +21,7 @@ From Wheel
|
|||
3. ``pip install <filename>.whl``
|
||||
|
||||
[OSX] From Homebrew
|
||||
----------
|
||||
-------------------
|
||||
|
||||
``brew install bandcamp-dl``
|
||||
|
||||
|
@ -46,14 +46,18 @@ Details
|
|||
::
|
||||
|
||||
Usage:
|
||||
bandcamp-dl.py <url>
|
||||
bandcamp-dl.py [--template=<template>] [--base-dir=<dir>]
|
||||
[--full-album]
|
||||
(<url> | --artist=<artist> --album=<album>)
|
||||
[--overwrite]
|
||||
[--no-art]
|
||||
bandcamp-dl.py (-h | --help)
|
||||
bandcamp-dl.py (--version)
|
||||
bandcamp-dl [url]
|
||||
bandcamp-dl [--template=<template>] [--base-dir=<dir>]
|
||||
[--full-album]
|
||||
(<url> | --artist=<artist> --album=<album>)
|
||||
[--overwrite]
|
||||
[--no-art]
|
||||
[--embed-lyrics]
|
||||
[--group]
|
||||
[--embed-art]
|
||||
[--debug]
|
||||
bandcamp-dl (-h | --help)
|
||||
bandcamp-dl (--version)
|
||||
|
||||
Options
|
||||
=======
|
||||
|
@ -61,15 +65,20 @@ Options
|
|||
::
|
||||
|
||||
Options:
|
||||
-h --help Show this screen.
|
||||
-v --version Show version.
|
||||
--artist=<artist> The artist's slug (from the URL)
|
||||
--album=<album> The album's slug (from the URL)
|
||||
--template=<template> Output filename template.
|
||||
[default: %{artist}/%{album}/%{track} - %{title}]
|
||||
--base-dir=<dir> Base location of which all files are downloaded
|
||||
-o --overwrite Overwrite tracks that already exist. Default is False.
|
||||
-n --no-art Skip grabbing album art
|
||||
-h --help Show this screen.
|
||||
-v --version Show version.
|
||||
-a --artist=<artist> The artist's slug (from the URL)
|
||||
-b --album=<album> The album's slug (from the URL)
|
||||
-t --template=<template> Output filename template.
|
||||
[default: %{artist}/%{album}/%{track} - %{title}]
|
||||
-d --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
|
||||
-e --embed-lyrics Embed track lyrics (If available)
|
||||
-g --group Use album/track Label as iTunes grouping
|
||||
-r --embed-art Embed album art (If available)
|
||||
-u --debug Log debug information to a file
|
||||
|
||||
Filename Template
|
||||
=================
|
||||
|
@ -90,10 +99,7 @@ Bugs
|
|||
====
|
||||
|
||||
Bugs should be reported `here <https://github.com/iheanyi/bandcamp-dl/issues>`_.
|
||||
Please include the full output of the command when run with ``--verbose``.
|
||||
The output (including the first lines) contain important debugging information.
|
||||
Issues without the full output are often not reproducible and therefore
|
||||
do not get solved in short order, if ever.
|
||||
Please include the URL and/or options used.
|
||||
|
||||
For discussions, join us in `Discord <https://discord.gg/nwdT4MP>`_.
|
||||
|
||||
|
@ -128,24 +134,6 @@ Many feature requests are for features that actually exist already!
|
|||
Please, absolutely do show off your work in the issue report and detail
|
||||
how the existing similar options do *not* solve your problem.
|
||||
|
||||
Is there enough context in your bug report?
|
||||
===========================================
|
||||
|
||||
People want to solve problems, and often think they do us a favor by
|
||||
breaking down their larger problems (e.g. wanting to skip already
|
||||
downloaded files) to a specific request (e.g. requesting us to look
|
||||
whether the file exists before downloading the info page). However, what
|
||||
often happens is that they break down the problem into two steps: One
|
||||
simple, and one impossible (or extremely complicated one).
|
||||
|
||||
We are then presented with a very complicated request when the original
|
||||
problem could be solved far easier, e.g. by recording the downloaded
|
||||
video IDs in a separate file. To avoid this, you must include the
|
||||
greater context where it is non-obvious. In particular, every feature
|
||||
request that does not consist of adding support for a new site should
|
||||
contain a use case scenario that explains in what situation the missing
|
||||
feature would be useful.
|
||||
|
||||
Does the issue involve one problem, and one problem only?
|
||||
=========================================================
|
||||
|
||||
|
@ -157,20 +145,10 @@ mark the issue as closed. Typically, reporting a bunch of issues leads
|
|||
to the ticket lingering since nobody wants to attack that behemoth,
|
||||
until someone mercifully splits the issue into multiple ones.
|
||||
|
||||
In particular, every site support request issue should only pertain to
|
||||
services at one site (generally under a common domain, but always using
|
||||
the same backend technology). Do not request support for vimeo user
|
||||
videos, Whitehouse podcasts, and Google Plus pages in the same issue.
|
||||
Also, make sure that you don't post bug reports alongside feature
|
||||
requests. As a rule of thumb, a feature request does not include outputs
|
||||
of bandcamp-dl that are not immediately related to the feature at hand.
|
||||
Do not post reports of a network error alongside the request for a new
|
||||
video service.
|
||||
|
||||
Is anyone going to need the feature?
|
||||
====================================
|
||||
|
||||
Only post features that you (or an incapacitated friend you can
|
||||
Only post features that you (or an incapable friend you can
|
||||
personally talk to) require. Do not post features because they seem like
|
||||
a good idea. If they are really useful, they will be requested by
|
||||
someone who requires them.
|
||||
|
|
|
@ -4,11 +4,12 @@ Usage:
|
|||
bandcamp-dl [url]
|
||||
bandcamp-dl [--template=<template>] [--base-dir=<dir>]
|
||||
[--full-album]
|
||||
(<url> | --artist=<artist> --album=<album>)
|
||||
(<url> | --artist=<artist> --album=<album> | --artist=<artist> --track=<track>)
|
||||
[--overwrite]
|
||||
[--no-art]
|
||||
[--embed-lyrics]
|
||||
[--group]
|
||||
[--embed-art]
|
||||
bandcamp-dl (-h | --help)
|
||||
bandcamp-dl (--version)
|
||||
|
||||
|
@ -16,6 +17,7 @@ Options:
|
|||
-h --help Show this screen.
|
||||
-v --version Show version.
|
||||
-a --artist=<artist> The artist's slug (from the URL)
|
||||
-s --track=<track> The track's slug (from the URL)
|
||||
-b --album=<album> The album's slug (from the URL)
|
||||
-t --template=<template> Output filename template.
|
||||
[default: %{artist}/%{album}/%{track} - %{title}]
|
||||
|
@ -25,6 +27,7 @@ Options:
|
|||
-n --no-art Skip grabbing album art
|
||||
-e --embed-lyrics Embed track lyrics (If available)
|
||||
-g --group Use album/track Label as iTunes grouping
|
||||
-r --embed-art Embed album art (If available)
|
||||
"""
|
||||
"""
|
||||
Coded by:
|
||||
|
@ -55,25 +58,9 @@ from docopt import docopt
|
|||
from bandcamp_dl.bandcamp import Bandcamp
|
||||
from bandcamp_dl.bandcampdownloader import BandcampDownloader
|
||||
|
||||
# LOGGING #######################
|
||||
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.setLevel(logging.DEBUG)
|
||||
|
||||
handler = logging.FileHandler('bandcamp-dl_0.0.7-09-DEBUG.log')
|
||||
handler.setLevel(logging.DEBUG)
|
||||
|
||||
logger.addHandler(handler)
|
||||
|
||||
|
||||
# LOGGING #######################
|
||||
|
||||
def main():
|
||||
arguments = docopt(__doc__, version='bandcamp-dl 0.0.7-09')
|
||||
|
||||
logger.debug('\n\tArguments: {}\n'.format(arguments))
|
||||
arguments = docopt(__doc__, version='bandcamp-dl 0.0.8')
|
||||
|
||||
bandcamp = Bandcamp()
|
||||
|
||||
|
@ -83,14 +70,16 @@ def main():
|
|||
if os.path.isfile(session_file):
|
||||
with open(session_file, "r") as f:
|
||||
arguments = ast.literal_eval(f.readline())
|
||||
elif arguments['<url>'] is None:
|
||||
elif arguments['<url>'] is None and arguments['--artist'] is None:
|
||||
print(__doc__)
|
||||
else:
|
||||
with open(session_file, "w") as f:
|
||||
f.write("".join(str(arguments).split('\n')))
|
||||
|
||||
if arguments['--artist'] and arguments['--album']:
|
||||
url = Bandcamp.generate_album_url(arguments['--artist'], arguments['--album'])
|
||||
url = Bandcamp.generate_album_url(arguments['--artist'], arguments['--album'], "album")
|
||||
elif arguments['--artist'] and arguments['--track']:
|
||||
url = Bandcamp.generate_album_url(arguments['--artist'], arguments['--track'], "track")
|
||||
else:
|
||||
url = arguments['<url>']
|
||||
|
||||
|
@ -105,8 +94,10 @@ def main():
|
|||
print("Full album not available. Skipping...")
|
||||
else:
|
||||
bandcamp_downloader = BandcampDownloader(url, arguments['--template'], basedir, arguments['--overwrite'],
|
||||
arguments['--embed-lyrics'], arguments['--group'])
|
||||
arguments['--embed-lyrics'], arguments['--group'],
|
||||
arguments['--embed-art'], arguments['--debug'])
|
||||
bandcamp_downloader.start(album)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
|
|
@ -7,22 +7,11 @@ from bs4 import FeatureNotFound
|
|||
|
||||
from bandcamp_dl.bandcampjson import BandcampJSON
|
||||
|
||||
# LOGGING #######################
|
||||
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.setLevel(logging.DEBUG)
|
||||
|
||||
handler = logging.FileHandler('bandcamp-dl_0.0.7-09-DEBUG.log')
|
||||
handler.setLevel(logging.DEBUG)
|
||||
|
||||
logger.addHandler(handler)
|
||||
|
||||
|
||||
# LOGGING #######################
|
||||
|
||||
class Bandcamp:
|
||||
def __init__(self, debug=False):
|
||||
self.debug = debug
|
||||
|
||||
def parse(self, url: str, art: bool=True) -> dict or None:
|
||||
"""Requests the page, cherry picks album info
|
||||
|
||||
|
@ -40,8 +29,6 @@ class Bandcamp:
|
|||
except FeatureNotFound:
|
||||
self.soup = BeautifulSoup(response.text, "html.parser")
|
||||
|
||||
logger.debug('\n\tBeautifulSoup: {}\n'.format(self.soup))
|
||||
|
||||
bandcamp_json = BandcampJSON(self.soup).generate()
|
||||
album_json = json.loads(bandcamp_json[0])
|
||||
embed_json = json.loads(bandcamp_json[1])
|
||||
|
@ -121,14 +108,15 @@ class Bandcamp:
|
|||
return track_metadata
|
||||
|
||||
@staticmethod
|
||||
def generate_album_url(artist: str, album: str) -> str:
|
||||
def generate_album_url(artist: str, slug: str, page_type: str) -> str:
|
||||
"""Generate an album url based on the artist and album name
|
||||
|
||||
:param artist: artist name
|
||||
:param album: album name
|
||||
:return: album url as str
|
||||
:param slug: Slug of album/track
|
||||
:param page_type: Type of page album/track
|
||||
:return: url as str
|
||||
"""
|
||||
return "http://{0}.bandcamp.com/album/{1}".format(artist, album)
|
||||
return "http://{0}.bandcamp.com/{1}/{2}".format(artist, page_type, slug)
|
||||
|
||||
def get_album_art(self) -> str:
|
||||
"""Find and retrieve album art url from page
|
||||
|
|
|
@ -6,6 +6,7 @@ from mutagen.mp3 import MP3, EasyMP3
|
|||
from mutagen.id3._frames import TIT1
|
||||
from mutagen.id3._frames import TIT2
|
||||
from mutagen.id3._frames import USLT
|
||||
from mutagen.id3._frames import APIC
|
||||
from slugify import slugify
|
||||
|
||||
if not sys.version_info[:2] == (3, 6):
|
||||
|
@ -14,7 +15,8 @@ if not sys.version_info[:2] == (3, 6):
|
|||
|
||||
|
||||
class BandcampDownloader:
|
||||
def __init__(self, urls=None, template=None, directory=None, overwrite=False, lyrics=None, grouping=None):
|
||||
def __init__(self, urls=None, template=None, directory=None, overwrite=False, lyrics=None, grouping=None,
|
||||
embed_art=None, debug=False):
|
||||
"""Initialize variables we will need throughout the Class
|
||||
|
||||
:param urls: list of urls
|
||||
|
@ -22,7 +24,7 @@ class BandcampDownloader:
|
|||
:param directory: download location
|
||||
:param overwrite: if True overwrite existing files
|
||||
"""
|
||||
self.headers = {'user_agent': 'bandcamp-dl/0.0.7-09 (https://github.com/iheanyi/bandcamp-dl)'}
|
||||
self.headers = {'user_agent': 'bandcamp-dl/0.0.8 (https://github.com/iheanyi/bandcamp-dl)'}
|
||||
self.session = requests.Session()
|
||||
|
||||
if type(urls) is str:
|
||||
|
@ -34,6 +36,8 @@ class BandcampDownloader:
|
|||
self.overwrite = overwrite
|
||||
self.lyrics = lyrics
|
||||
self.grouping = grouping
|
||||
self.embed_art = embed_art
|
||||
self.debug = debug
|
||||
|
||||
def start(self, album: dict):
|
||||
"""Start album download process
|
||||
|
@ -60,10 +64,12 @@ class BandcampDownloader:
|
|||
path = self.template
|
||||
path = path.replace("%{artist}", slugify(track['artist']))
|
||||
path = path.replace("%{album}", slugify(track['album']))
|
||||
|
||||
if track['track'] == "None":
|
||||
path = path.replace("%{track}", "Single")
|
||||
else:
|
||||
path = path.replace("%{track}", str(track['track']).zfill(2))
|
||||
|
||||
path = path.replace("%{title}", slugify(track['title']))
|
||||
path = u"{0}/{1}.{2}".format(self.directory, path, "mp3")
|
||||
|
||||
|
@ -108,6 +114,16 @@ class BandcampDownloader:
|
|||
filename = filepath.rsplit('/', 1)[1]
|
||||
dirname = self.create_directory(filepath)
|
||||
|
||||
if album['art'] and not os.path.exists(dirname + "/cover.jpg"):
|
||||
try:
|
||||
with open(dirname + "/cover.jpg", "wb") as f:
|
||||
r = self.session.get(album['art'])
|
||||
f.write(r.content)
|
||||
self.album_art = dirname + "/cover.jpg"
|
||||
except Exception as e:
|
||||
print(e)
|
||||
print("Couldn't download album art.")
|
||||
|
||||
attempts = 0
|
||||
skip = False
|
||||
|
||||
|
@ -165,17 +181,14 @@ class BandcampDownloader:
|
|||
return False
|
||||
if skip is not True:
|
||||
self.write_id3_tags(filepath, track_meta)
|
||||
if album['art']:
|
||||
try:
|
||||
with open(dirname + "/cover.jpg", "wb") as f:
|
||||
r = self.session.get(album['art'])
|
||||
f.write(r.content)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
print("Couldn't download album art.")
|
||||
|
||||
if os.path.isfile("not.finished"):
|
||||
os.remove("not.finished")
|
||||
|
||||
# Remove album art image as it is embedded
|
||||
if self.embed_art:
|
||||
os.remove(self.album_art)
|
||||
|
||||
return True
|
||||
|
||||
def write_id3_tags(self, filepath: str, meta: dict):
|
||||
|
@ -199,6 +212,10 @@ class BandcampDownloader:
|
|||
audio["TIT1"] = TIT1(encoding=3, text=meta["label"])
|
||||
if self.lyrics:
|
||||
audio["USLT"] = USLT(encoding=3, lang='eng', desc='', text=meta['lyrics'])
|
||||
if self.embed_art:
|
||||
with open(self.album_art, 'rb') as cover_img:
|
||||
cover_bytes = cover_img.read()
|
||||
audio["APIC"] = APIC(encoding=3, mime='image/jpeg', type=3, desc='Cover', data=cover_bytes)
|
||||
audio.save()
|
||||
|
||||
audio = EasyMP3(filepath)
|
||||
|
|
|
@ -2,20 +2,6 @@ import re
|
|||
|
||||
import demjson
|
||||
|
||||
# LOGGING #######################
|
||||
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
logger.setLevel(logging.DEBUG)
|
||||
|
||||
handler = logging.FileHandler('bandcamp-dl_0.0.7-09-DEBUG.log')
|
||||
handler.setLevel(logging.DEBUG)
|
||||
|
||||
logger.addHandler(handler)
|
||||
|
||||
|
||||
# LOGGING #######################
|
||||
|
||||
class BandcampJSON:
|
||||
def __init__(self, body):
|
||||
|
@ -32,7 +18,6 @@ class BandcampJSON:
|
|||
self.regex = re.compile(r"(?<=var\s" + target + "\s=\s).*?(?=};)", re.DOTALL)
|
||||
self.target = target
|
||||
self.js_to_json()
|
||||
logger.debug('\n\tPreliminary JSON data: {}\n'.format(self.json_data))
|
||||
return self.json_data
|
||||
|
||||
def get_pagedata(self):
|
||||
|
|
2
setup.py
2
setup.py
|
@ -10,7 +10,7 @@ here = path.abspath(path.dirname(__file__))
|
|||
|
||||
setup(
|
||||
name='bandcamp-downloader',
|
||||
version='0.0.7-09',
|
||||
version='0.0.8',
|
||||
description='bandcamp-dl downloads albums and tracks from Bandcamp for you',
|
||||
long_description=open('README.rst').read(),
|
||||
url='https://github.com/iheanyi/bandcamp-dl',
|
||||
|
|
Loading…
Reference in New Issue