__main__.py 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. import argparse
  2. import logging
  3. import pathlib
  4. import sys
  5. from bandcamp_dl import __version__
  6. from bandcamp_dl.bandcamp import Bandcamp
  7. from bandcamp_dl.bandcampdownloader import BandcampDownloader
  8. from bandcamp_dl import config
  9. from urllib.parse import urlparse
  10. def main():
  11. # parse config if found, else create it
  12. conf = config.Config()
  13. parser = argparse.ArgumentParser()
  14. parser.add_argument('URL', help="Bandcamp album/track URL", nargs="*")
  15. parser.add_argument('-v', '--version', action='store_true', help='Show version')
  16. parser.add_argument('-d', '--debug', action='store_true', help='Verbose logging', default=conf.debug)
  17. parser.add_argument('--artist', help="Specify an artist's slug to download their full discography.")
  18. parser.add_argument('--track', help="Specify a track's slug to download a single track. Must be used with --artist.")
  19. parser.add_argument('--album', help="Specify an album's slug to download a single album. Must be used with --artist.")
  20. parser.add_argument('--template', help=f"Output filename template, default: "
  21. f"{conf.template.replace('%', '%%')}", default=conf.template)
  22. parser.add_argument('--base-dir', help='Base location of which all files are downloaded',
  23. default=conf.base_dir)
  24. parser.add_argument('-f', '--full-album', help='Download only if all tracks are available',
  25. action='store_true')
  26. parser.add_argument('-o', '--overwrite', action='store_true',
  27. help=f'Overwrite tracks that already exist. Default is {conf.overwrite}.',
  28. default=conf.overwrite)
  29. parser.add_argument('-n', '--no-art', help='Skip grabbing album art', action='store_true',
  30. default=conf.no_art)
  31. parser.add_argument('-e', '--embed-lyrics', help='Embed track lyrics (If available)',
  32. action='store_true', default=conf.embed_lyrics)
  33. parser.add_argument('-g', '--group', help='Use album/track Label as iTunes grouping',
  34. action='store_true', default=conf.group)
  35. parser.add_argument('-r', '--embed-art', help='Embed album art (If available)',
  36. action='store_true', default=conf.embed_art)
  37. parser.add_argument('--cover-quality', help='Set the cover art quality. 0 is source, 10 is album page (1200x1200), 16 is default embed (700x700).',
  38. default=conf.cover_quality, type=int, choices=[0, 10, 16])
  39. parser.add_argument('--untitled-path-from-slug', help='For albums titled untitled, use the URL slug to generate the folder path.',
  40. action='store_true', default=conf.untitled_path_from_slug)
  41. parser.add_argument('-y', '--no-slugify', action='store_true', default=conf.no_slugify,
  42. help='Disable slugification of track, album, and artist names')
  43. parser.add_argument('-c', '--ok-chars', default=conf.ok_chars,
  44. help=f'Specify allowed chars in slugify, default: {conf.ok_chars}')
  45. parser.add_argument('-s', '--space-char', help=f'Specify the char to use in place of spaces, '
  46. f'default: {conf.space_char}', default=conf.space_char)
  47. parser.add_argument('-a', '--ascii-only', help='Only allow ASCII chars (北京 (capital of '
  48. 'china) -> bei-jing-capital-of-china)', action='store_true',
  49. default=conf.ascii_only)
  50. parser.add_argument('-k', '--keep-spaces', help='Retain whitespace in filenames',
  51. action='store_true', default=conf.keep_spaces)
  52. parser.add_argument('-x', '--case-convert', help=f'Specify the char case conversion logic, '
  53. f'default: {conf.case_mode}', default=conf.case_mode, dest='case_mode',
  54. choices=[config.CASE_LOWER, config.CASE_UPPER, config.CASE_CAMEL,
  55. config.CASE_NONE])
  56. parser.add_argument('--no-confirm', help='Override confirmation prompts. Use with caution',
  57. action='store_true', default=conf.no_confirm)
  58. parser.add_argument('--embed-genres', help='Embed album/track genres',
  59. action='store_true', default=conf.embed_genres)
  60. parser.add_argument('--truncate-album', metavar='LENGTH', type=int, default=0,
  61. help='Truncate album title to a maximum length. 0 for no limit.')
  62. parser.add_argument('--truncate-track', metavar='LENGTH', type=int, default=0,
  63. help='Truncate track title to a maximum length. 0 for no limit.')
  64. arguments = parser.parse_args()
  65. if arguments.version:
  66. sys.stdout.write(f"bandcamp-dl {__version__}\n")
  67. return
  68. if arguments.debug:
  69. logging.basicConfig(level=logging.DEBUG)
  70. else:
  71. logging.basicConfig()
  72. logging_handle = "bandcamp-dl"
  73. logger = logging.getLogger(logging_handle)
  74. # TODO: Its possible to break bandcamp-dl temporarily by simply erasing a line in the config, catch this and warn.
  75. logger.debug(f"Config/Args: {arguments}")
  76. if not arguments.URL and not arguments.artist:
  77. parser.print_usage()
  78. sys.stderr.write(f"{pathlib.Path(sys.argv[0]).name}: error: the following arguments are "
  79. f"required: URL or --artist\n")
  80. sys.exit(2)
  81. for arg, val in [('base_dir', config.USER_HOME), ('template', config.TEMPLATE),
  82. ('ok_chars', config.OK_CHARS), ('space_char', config.SPACE_CHAR)]:
  83. if not getattr(arguments, arg):
  84. setattr(arguments, arg, val)
  85. bandcamp = Bandcamp()
  86. if arguments.artist and arguments.album:
  87. urls = Bandcamp.generate_album_url(arguments.artist, arguments.album, "album")
  88. elif arguments.artist and arguments.track:
  89. urls = Bandcamp.generate_album_url(arguments.artist, arguments.track, "track")
  90. elif arguments.artist:
  91. urls = Bandcamp.get_full_discography(bandcamp, arguments.artist, "music")
  92. else:
  93. urls = []
  94. for url in arguments.URL:
  95. parsed_url = urlparse(url)
  96. if parsed_url.netloc.endswith('.bandcamp.com') and (parsed_url.path == '/music' or parsed_url.path == '/' or parsed_url.path == ''):
  97. artist = parsed_url.netloc.split('.')[0]
  98. print(f"Found artist page, fetching full discography for: {artist}")
  99. urls.extend(bandcamp.get_full_discography(artist, "music"))
  100. else:
  101. urls.append(url)
  102. album_list = []
  103. for url in urls:
  104. if "/album/" not in url and "/track/" not in url:
  105. continue
  106. logger.debug("\n\tURL: %s", url)
  107. album_list.append(bandcamp.parse(url, not arguments.no_art, arguments.embed_lyrics, arguments.embed_genres,
  108. arguments.debug, arguments.cover_quality))
  109. for album in album_list:
  110. logger.debug(f" Album data:\n\t{album}")
  111. if arguments.full_album and not album['full']:
  112. print("Full album not available. Skipping ", album['title'], " ...")
  113. # Remove not-full albums BUT continue with the rest of the albums.
  114. album_list.remove(album)
  115. if arguments.URL or arguments.artist:
  116. logger.debug("Preparing download process..")
  117. for album in album_list:
  118. bandcamp_downloader = BandcampDownloader(arguments, album['url'])
  119. logger.debug("Initiating download process..")
  120. bandcamp_downloader.start(album)
  121. # Add a newline to stop prompt mangling
  122. print("")
  123. else:
  124. logger.debug(r" /!\ Something went horribly wrong /!\ ")
  125. if __name__ == '__main__':
  126. main()