import argparse, os, hashlib, random, mutagen, progressbar, ffmpeg # get input parser = argparse.ArgumentParser(description="automatically create shuffled music playlist file from folder") parser.add_argument("input", help="input folder", metavar="input", type=str) parser.add_argument("output", help="output file", metavar="output", type=str) parser.add_argument("-d", "--dedup", help="deduplication mode (sha1/filename)", required=False, default="sha1", type=str) parser.add_argument("-t", "--outputtype", help="output type (onefile/folder)", required=False, default="onefile", type=str) parser.add_argument("-f", "--force", help="overwrite without asking", required=False, type=bool, default=False) args = parser.parse_args() # make sure arguments are valid if (not os.path.isdir(args.input)): print("input folder does not exist") exit(1) if (args.dedup != "sha1" and args.dedup != "filename"): print("deduplication mode is not valid (sha1/filename)") exit(1) if (args.outputtype != "onefile" and args.outputtype != "folder"): print("output type is not valid (onefile/folder)") exit(1) # check if output exists already, warn user about potential overwriting if (args.outputtype == "onefile"): if (os.path.isfile(args.output) and not args.force): print("output file already exists, overwrite? (y/n)") answer = input() if (answer.lower() != "y"): exit() else: if (os.path.isdir(args.output) and not args.force): print("output folder already exists, overwrite files inside? (y/n)") answer = input() if (answer.lower() != "y"): exit() print("fyi: this doesnt delete files in the folder, only overwrites songs already present") # scan input folder f_files = [] f_special = [] allowedfiletypes = ["mp3", "opus", "flac", "wav", "aac", "m4a"] # what else? print("scanning folders...") pbar = progressbar.ProgressBar(max_value=progressbar.UnknownLength) def scanfolder(folder): # goes through a folder. checks all the files in it # then recursively goes through all subfolders progress = 0 dirs_to_recurse = [] global f_files, f_special for file in os.listdir(folder): if (os.path.isfile(folder + "/" + file)): # make sure file is audio file if (file.split(".")[-1].lower() in allowedfiletypes): # verify not already in list if (args.dedup == "sha1"): hash = hashlib.sha1(open(folder + "/" + file, "rb").read()).hexdigest() if (hash not in f_special): f_special.append(hash) f_files.append(folder + "/" + file) progress += 1 elif (args.dedup == "filename"): if (file not in f_special): f_special.append(file) f_files.append(folder + "/" + file) elif (os.path.isdir(folder + "/" + file)): dirs_to_recurse.append(folder + "/" + file) pbar.update(progress) for dir in dirs_to_recurse: scanfolder(dir) scanfolder(args.input) pbar.finish() del f_special if (len(f_files) == 0): print("no (supported) audio files found in input folder") exit(1) random.shuffle(f_files) # scan through files. count song length. # save every scanned file to a new array. # once we finish scanning, or total length exceeds eight hours, finish maxlength = 8 * 60 * 60 # 8 hours length = 0.0 f_final = [] print("generating playlist...") pbar = progressbar.ProgressBar(max_value=maxlength, redirect_stdout=True) for x in range(len(f_files)): try: audio = mutagen.File(f_files[x]) length += audio.info.length f_final.append(f_files[x]) except: print("skipping " + f_files[x] + " (not valid file according to mutagen)") if (length >= maxlength): pbar.update(maxlength) break else: pbar.update(int(length)) pbar.finish() del f_files if (length < maxlength): print("warning: not enough songs to fill 8 hours") # create output folder if needed if (args.outputtype == "folder"): if (not os.path.isdir(args.output)): os.mkdir(args.output) # start encoding if (args.outputtype == "onefile"): f_inputs = [] print("encoding file...") for file in f_final: f_inputs.append(ffmpeg.input(file)) stream = ffmpeg.concat(*f_inputs, v=0, a=1) # make sure args.output contains a file extension if (args.output.split(".")[-1] not in allowedfiletypes): stream = ffmpeg.output(stream, args.output + ".opus") else: stream = ffmpeg.output(stream, args.output) ffmpeg.run(stream, overwrite_output=True, quiet=True) else: filestoremove = [] print("encoding files to folder...") for file in f_final: try: stream = ffmpeg.input(file) stream = ffmpeg.output(stream, args.output + "/" + os.path.basename(file) + ".opus") ffmpeg.run(stream, overwrite_output=True, quiet=True) except: print("skipping " + file + " (not valid file according to ffmpeg)") filestoremove.append(args.output + "/" + os.path.basename(file) + ".opus") if (len(filestoremove) > 0): print("removing invalid files (interactive)...") for file in filestoremove: if (os.path.isfile(file)): q = input("remove " + file + "? (y/n) ") if (q.lower() == "y"): os.remove(file) # done print("all done! :3")