Skip to content

Commit

Permalink
Config support
Browse files Browse the repository at this point in the history
A configuration file is now generated on first run and can be edited by the user, bandcamp-dl will default to these settings unless an arg is passed by the user.
  • Loading branch information
Evolution0 committed Feb 18, 2023
1 parent 1f357a8 commit 9f22fbe
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 58 deletions.
3 changes: 3 additions & 0 deletions .idea/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 8 additions & 19 deletions bandcamp_dl/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,40 +56,35 @@

import os
import ast
import json
import logging
import importlib
from docopt import docopt
import bandcamp_dl.bandcamp
from bandcamp_dl.bandcamp import Bandcamp
from bandcamp_dl.bandcampdownloader import BandcampDownloader
from bandcamp_dl.utils.config import init_config
from bandcamp_dl.__init__ import __version__


def main():
arguments = docopt(__doc__, version=f'bandcamp-dl {__version__}')

if arguments['--debug']:
logging.basicConfig(level=logging.DEBUG)

bandcamp = Bandcamp()

basedir = arguments['--base-dir'] or os.path.expanduser('~')
session_file = f"{basedir}/{__version__}.not.finished"
# TODO: Its possible to break bandcamp-dl temporarily by simply erasing a line in the config, catch this and warn.
config = init_config(arguments)

if os.path.isfile(session_file) and arguments['URL'] is None:
with open(session_file, "r") as f:
arguments = ast.literal_eval(f.readline())
else:
with open(session_file, "w") as f:
f.write("".join(str(arguments).split('\n')))
if config['debug']:
logging.basicConfig(level=logging.DEBUG)

if arguments['--artist'] and arguments['--album']:
urls = Bandcamp.generate_album_url(arguments['--artist'], arguments['--album'], "album")
elif arguments['--artist'] and arguments['--track']:
urls = Bandcamp.generate_album_url(arguments['--artist'], arguments['--track'], "track")
elif arguments['--artist']:
print(__doc__)
os.remove(session_file)
os.remove(f"{config['basedir']}/{__version__}.not.finished")
exit()
else:
urls = arguments['URL']
Expand All @@ -116,13 +111,7 @@ def main():
if arguments['URL'] or arguments['--artist']:
logging.debug("Preparing download process..")
for album in album_list:
bandcamp_downloader = BandcampDownloader(arguments['--template'], basedir, arguments['--overwrite'],
arguments['--embed-lyrics'], arguments['--group'],
arguments['--embed-art'], arguments['--no-slugify'],
arguments['--ok-chars'], arguments['--space-char'],
arguments['--ascii-only'], arguments['--keep-spaces'],
arguments['--keep-upper'], arguments['--debug'],
arguments['--no-confirm'], album['url'])
bandcamp_downloader = BandcampDownloader(config, album['url'])
logging.debug("Initiating download process..")
bandcamp_downloader.start(album)
# Add a newline to stop prompt mangling
Expand Down
59 changes: 21 additions & 38 deletions bandcamp_dl/bandcampdownloader.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,46 +20,30 @@


class BandcampDownloader:
def __init__(self, template, directory, overwrite, embed_lyrics, grouping, embed_art, no_slugify, ok_chars,
space_char, ascii_only, keep_space, keep_upper, debugging, no_confirm, urls=None):
def __init__(self, config, urls=None):
"""Initialize variables we will need throughout the Class
:param config: user config/args
:param urls: list of urls
:param template: filename template
:param directory: download location
:param overwrite: if True overwrite existing files
"""
self.headers = {'User-Agent': f'bandcamp-dl/{__version__} (https://github.com/iheanyi/bandcamp-dl)'}
self.session = requests.Session()

if type(urls) is str:
self.urls = [urls]

self.config = config
self.urls = urls
self.template = template
self.directory = directory
self.overwrite = overwrite
self.grouping = grouping
self.embed_art = embed_art
self.embed_lyrics = embed_lyrics
self.no_slugify = no_slugify
self.ok_chars = ok_chars
self.space_char = space_char
self.ascii_only = ascii_only
self.keep_space = keep_space
self.keep_upper = keep_upper
self.debugging = debugging
self.confirmation_skip = no_confirm

def start(self, album: dict):
"""Start album download process
:param album: album dict
"""
if self.debugging:
if self.config['debug']:
logging.basicConfig(level=logging.DEBUG)

if not album['full'] and not self.confirmation_skip:
if not album['full'] and not self.config['confirmation_skip']:
choice = input("Track list incomplete, some tracks may be private, download anyway? (yes/no): ").lower()
if choice == "yes" or choice == "y":
print("Starting download process.")
Expand All @@ -82,14 +66,14 @@ def template_to_path(self, track: dict, ascii_only, ok_chars, space_char, keep_s
:return: filepath
"""
logging.debug(" Generating filepath/trackname..")
path = self.template
path = self.config['template']

def slugify_preset(content):
slugged = slugify(content, ok=ok_chars, only_ascii=ascii_only, spaces=keep_space, lower=not keep_upper,
space_replacement=space_char)
return slugged

if self.no_slugify:
if self.config['no_slugify']:
path = path.replace("%{artist}", track['artist'])
path = path.replace("%{album}", track['album'])
path = path.replace("%{title}", track['title'])
Expand All @@ -107,9 +91,7 @@ def slugify_preset(content):
else:
path = path.replace("%{track}", str(track['track']).zfill(2))

# Double check that the old issue in Python 2 with unicode strings isn't a problem with f-strings
# Otherwise find an alternative to u'STRING'
path = f"{self.directory}/{path}.mp3"
path = f"{self.config['basedir']}/{path}.mp3"

logging.debug(" filepath/trackname generated..")
logging.debug(f"\n\tPath: {path}")
Expand Down Expand Up @@ -151,8 +133,9 @@ def download_album(self, album: dict) -> bool:
self.num_tracks = len(album['tracks'])
self.track_num = track_index + 1

filepath = self.template_to_path(track_meta, self.ascii_only, self.ok_chars, self.space_char,
self.keep_space, self.keep_upper) + ".tmp"
filepath = self.template_to_path(track_meta, self.config['ascii_only'], self.config['allowed_chars'],
self.config['space_char'], self.config['keep_spaces'],
self.config['keep_upper']) + ".tmp"
filename = filepath.rsplit('/', 1)[1]
dirname = self.create_directory(filepath)

Expand Down Expand Up @@ -187,7 +170,7 @@ def download_album(self, album: dict) -> bool:
skip = True
# break out of the try/except and move on to the next file
break
elif os.path.exists(filepath[:-4]) and self.overwrite is not True:
elif os.path.exists(filepath[:-4]) and self.config['overwrite'] is not True:
print(f"File: {filename[:-4]} already exists and is complete, skipping..")
skip = True
break
Expand All @@ -199,7 +182,7 @@ def download_album(self, album: dict) -> bool:
for data in r.iter_content(chunk_size=total):
dl += len(data)
f.write(data)
if not self.debugging:
if not self.config['debug']:
done = int(50 * dl / file_length)
print_clean(
f'\r({self.track_num}/{self.num_tracks}) [{"=" * done}{" " * (50 - done)}] :: Downloading: {filename[:-8]}')
Expand All @@ -224,11 +207,11 @@ def download_album(self, album: dict) -> bool:
if skip is False:
self.write_id3_tags(filepath, track_meta)

if os.path.isfile(f"{self.directory}/{__version__}.not.finished"):
os.remove(f"{self.directory}/{__version__}.not.finished")
if os.path.isfile(f"{self.config['basedir']}/{__version__}.not.finished"):
os.remove(f"{self.config['basedir']}/{__version__}.not.finished")

# Remove album art image as it is embedded
if self.embed_art:
if self.config['embed_art']:
os.remove(self.album_art)

return True
Expand All @@ -243,7 +226,7 @@ def write_id3_tags(self, filepath: str, meta: dict):

filename = filepath.rsplit('/', 1)[1][:-8]

if not self.debugging:
if not self.config['debug']:
print_clean(f'\r({self.track_num}/{self.num_tracks}) [{"=" * 50}] :: Encoding: {filename}')

audio = MP3(filepath)
Expand All @@ -252,13 +235,13 @@ def write_id3_tags(self, filepath: str, meta: dict):
audio.save(filename=None, v1=2)

audio = MP3(filepath)
if self.grouping and 'label' in meta:
if self.config['group'] and 'label' in meta:
audio["TIT1"] = TIT1(encoding=3, text=meta["label"])

if self.embed_lyrics:
if self.config['embed_lyrics']:
audio["USLT"] = USLT(encoding=3, lang='eng', desc='', text=meta['lyrics'])

if self.embed_art:
if self.config['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)
Expand All @@ -281,5 +264,5 @@ def write_id3_tags(self, filepath: str, meta: dict):
os.remove(filepath[:-4])
os.rename(filepath, filepath[:-4])

if not self.debugging:
if not self.config['debug']:
print_clean(f'\r({self.track_num}/{self.num_tracks}) [{"=" * 50}] :: Finished: {filename}')
109 changes: 109 additions & 0 deletions bandcamp_dl/utils/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import logging
import os
import ast
import json
from bandcamp_dl.__init__ import __version__

user_home = os.path.expanduser('~')

if os.name == "posix":
# Reasoning: https://www.freedesktop.org/wiki/Software/xdg-user-dirs/
config_dir = ".config"
else:
# .appname is fine in Windows and MacOS
config_dir = ".bandcamp-dl"

config_path = f"{user_home}/{config_dir}/bandcamp-dl.json"


def init_config(arguments) -> json or dict:
"""Create and populate a default config, otherwise load it"""
if os.path.isfile(config_path):
logging.debug("Config exists, loading...")
with open(config_path, "r") as config_file:
config = json.load(config_file)
first_run = False
else:
if os.path.exists(f"{user_home}/{config_dir}") is False:
logging.debug("Config dir doesn't exist, creating...")
os.mkdir(f"{user_home}/{config_dir}")
first_run = True
with open(config_path, "w") as config_file:
# Where config defaults are set/added
config = {
"basedir": user_home,
"template": "%{artist}/%{album}/%{track} - %{title}",
"overwrite": False,
"no_art": False,
"embed_art": False,
"embed_lyrics": False,
"group": False,
"no_slugify": False,
"allowed_chars": "-_~",
"space_char": "-",
"ascii_only": False,
"keep_spaces": False,
"keep_upper": False,
"confirmation_skip": False,
"debug": False
}
config_json = json.dumps(config, indent=4)
logging.debug("Creating config file...")
config_file.write(config_json)

# TODO: Session file should override config, as its essentially replaying manually set args
session_file = f"{config['basedir']}/{__version__}.not.finished"

if os.path.isfile(session_file) and arguments['URL'] is None:
with open(session_file, "r") as f:
arguments = ast.literal_eval(f.readline())
elif first_run is False:
with open(session_file, "w") as f:
f.write("".join(str(arguments).split('\n')))

if arguments['--base-dir'] is not None:
config['basedir'] = arguments['--base-dir']

if arguments['--template'] != config['template']:
config['template'] = arguments['--template']

if arguments['--overwrite'] != config['overwrite']:
config['overwrite'] = arguments['--overwrite']

if arguments['--no-art'] != config['no_art']:
config['no_art'] = arguments['--no-art']

if arguments['--embed-art'] != config['embed_art']:
config['embed_art'] = arguments['--embed-art']

if arguments['--embed-lyrics'] != config['embed_lyrics']:
config['embed_lyrics'] = arguments['--embed-lyrics']

if arguments['--group'] != config['group']:
config['group'] = arguments['--group']

if arguments['--no-slugify'] != config['no_slugify']:
config['no_slugify'] = arguments['--no-slugify']

if arguments['--ok-chars'] != config['allowed_chars']:
config['allowed_chars'] = arguments['--ok-chars']

if arguments['--space-char'] != config['space_char']:
config['space_char'] = arguments['--space-char']

if arguments['--ascii-only'] != config['ascii_only']:
config['ascii_only'] = arguments['--ascii-only']

if arguments['--keep-spaces'] != config['keep_spaces']:
config['keep_spaces'] = arguments['--keep-spaces']

if arguments['--keep-upper'] != config['keep_upper']:
config['keep_upper'] = arguments['--keep-upper']

if arguments['--no-confirm'] != config['confirmation_skip']:
config['confirmation_skip'] = arguments['--no-confirm']

if arguments['--debug'] != config['debug']:
config['debug'] = arguments['--debug']

return config
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from setuptools import setup, find_packages
import pathlib

appversion = "0.0.12"
appversion = "0.0.14"

here = pathlib.Path(__file__).parent.resolve()

Expand Down

0 comments on commit 9f22fbe

Please sign in to comment.