From 25ad99c81611579a579296739e347321de022956 Mon Sep 17 00:00:00 2001 From: Lynne Date: Sun, 5 May 2019 03:55:32 +1000 Subject: [PATCH] commit de la init --- bcao.py | 166 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100755 bcao.py diff --git a/bcao.py b/bcao.py new file mode 100755 index 0000000..41c7b1d --- /dev/null +++ b/bcao.py @@ -0,0 +1,166 @@ +#!/usr/bin/env python3 +#BCAO - BandCamp Automatic Organiser +#copyright 2018 lynnear software babyyyyyyyyyyyy +#GPLv3 because why the fuck not +#input: a .zip from bandcamp +#output: it organises it, adds cover art, puts it in the right place... + +import subprocess, argparse, sys, os, re, base64 + +try: + from mutagen.oggvorbis import OggVorbis + from mutagen.mp3 import MP3 + from mutagen.flac import FLAC + from mutagen.flac import Picture + from mutagen.aac import AAC +except ImportError: + print("Please install python3-mutagen (pip install mutagen)") + sys.exit(1) + +try: + subprocess.check_output(["unzip", "--help"]) +except: + print("Please install unzip (apt install unzip)") + sys.exit(1) + +try: + subprocess.check_output(["which", "convert"]) + subprocess.check_output(["which", "identify"]) +except: + print("Please install imagemagick, and ensure convert and identify are in your $PATH (apt install imagemagick") + sys.exit(1) + +parser = argparse.ArgumentParser(description = "BandCamp Automatic Organiser. Extracts the given zip file downloaded from Bandcamp and organises it.") +parser.add_argument('zip', help='The zip file to use') +#KEEP THESE IN ALPHABETICAL ORDER! +parser.add_argument('-q','--quiet', dest='quiet', action='store_true', help='Disable non-error output') +parser.add_argument('-t','--threshold', dest='threshold', nargs=1, default=300, help="Maximum acceptable cover art file size in kilobytes. Default: 300") + +args=parser.parse_args() + +if not os.path.exists(args.zip): + print("heh.... nice try kid... {0} aint a real file... i've been aroud the block a few times, ya know... you'll have to do a lot better than that to trick me.... kid...".format(args.zip)) + sys.exit(2) + +print("Extracting...") +zipname = os.path.splitext(os.path.basename(args.zip))[0] +tmp = "/tmp/bcao/{0}".format(zipname) +subprocess.check_output(["rm", "-rf", tmp]) +subprocess.check_output(['mkdir', '-p', tmp]) +subprocess.check_output(["unzip", args.zip, "-d", tmp]) +files = [] +songs = [] + +for root, dirs, filez in os.walk(tmp): + for file in filez: #for every file + files.append(root + os.sep + file) + +cover = "" +artists = [] +album = "" +fileExtensionRegex = re.compile(r"[^\.]+$") +probablyASongRegex = re.compile(r"^.+ - .+ - \d{2,} .+\.[^\.]+$") #matches "artist - album - 01 track.xyz" but not "some weird bonus thing.mp3" +musicExts = ["ogg", "flac", "alac", "aiff", "wav", "mp3", "opus", "m4a", "aac", "oga"] +bannedCharacters = ["?", "\\", "/", ":", "|", "*", "\"", "<", ">"] #characters that kill wangblows. all of these are fine on lincucks/crapOS except "/" +trackCount = 0 + +print("Processing... please wait.") +#use "01 Song.ogg" instead of "1 Song.ogg". Also works for albums with more than 99 tracks if that ever happens +trackNumberLength = len(str(len(files))) +if trackNumberLength < 2: + trackNumberLength = 2 + +for file in files: + if os.path.basename(file).lower() in ["cover.png", "cover.jpg", "cover.gif"]: + #we've found our cover art + cover = file + ext = re.search(fileExtensionRegex, file.lower()).group(0) + if ext in musicExts: + if re.search(probablyASongRegex, file) != None: + #this is definitely a song and probably not a bonus sound file or whatever + if ext == "ogg": + f = OggVorbis(file) + name = "{0} {1}.{2}".format(f["TRACKNUMBER"][0].zfill(trackNumberLength), f["TITLE"][0], ext) + for bc in bannedCharacters: + if bc in name: + name = name.replace(bc, "-") #replace banned characters with dashes + songs.append(name) + if album == "": + album = f["ALBUM"][0] + if f["ARTIST"][0] not in artists: + artists.append(f["ARTIST"][0]) + trackCount = trackCount + 1 + subprocess.check_output(["mv", file, os.path.dirname(file) + os.sep + name]) + + else: + print("UNSUPPORTED FORMAT BECAUSE LYNNE IS A LAZY MORON") +if len(artists) > 1: + artists.append("Various Artists") + +if cover == "": + #TODO: HANDLE THIS PROPERLY + print("couldn't find the cover art :)))))") + sys.exit(1) + +while os.path.getsize(cover) / 1024 > args.threshold: + if os.path.basename(cover) != "cover-lq.jpg": + nucover = os.path.dirname(cover) + os.sep + "cover-lq.jpg" + subprocess.check_output(["convert", cover, "-quality", "85", nucover]) #convert the file to a jpeg + cover = nucover + else: + subprocess.check_output(["convert", cover, "-resize", "90%", cover]) #shrink it slightly + +with open(cover, "rb") as cvr: + data = cvr.read() + +imginfo = subprocess.check_output(["identify", "-ping", "-format", r"%w,%h,%m,%[bit-depth]", cover]).decode("utf-8").split(",") + +picture = Picture() +picture.data = data +picture.type = 3 +picture.mime = "image/{0}".format(imginfo[2].lower()) +picture.width = int(imginfo[0]) +picture.height = int(imginfo[1]) +picture.depth = int(imginfo[3]) + +picture_data = picture.write() +encoded_data = base64.b64encode(picture_data) +vcomment_value = encoded_data.decode("ascii") + +for song in songs: + f = OggVorbis(tmp + os.sep + song) + f["metadata_block_picture"] = [vcomment_value] + f.save() + +artist = artists[0] +artists.append("Custom...") + +# print("Please choose the artist name to use when creating the folder.") +choice = 0 +while True: + print("Artist directory:") + for i in range(len(artists)): + print("{0}) {1}".format(i + 1, artists[i])) + choice = input("> ") + try: + choice = artists[int(choice) - 1] + except: + print() + continue + break +if choice == "Custom...": + print("Enter the name to use.") + choice = input("> ") +# print("Setting artist to {0}".format(choice)) +artist = choice + +mPath = "/home/lynne/Music/Music/{0}/{1}".format(artist, album) #todo: don't hardcode this +subprocess.check_output(["mkdir", "-p", mPath]) + +for root, dirs, filez in os.walk(tmp): + for file in filez: #for every file + subprocess.check_output(["mv", root + os.sep + file, mPath + os.sep + file]) + +subprocess.check_output(["rm", "-rf", tmp]) + +print("Done!") \ No newline at end of file