commit 8cb8c598ba0680511cbb5def2afa4e81d477b993 Author: remi Date: Wed May 4 18:05:09 2022 +0200 a diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ce2c4d2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.opus +.DS_Store \ No newline at end of file diff --git a/outline.md b/outline.md new file mode 100644 index 0000000..79373db --- /dev/null +++ b/outline.md @@ -0,0 +1,26 @@ +# PROBLEM +i want tunes + +# SOLUTION +ipod_shuffle.py (now renamed to peegen4k,,) + +* inputs is a directory +* program looks thru directory and subfolders for every .mp3 (and flac, and so on) file +* de-duplicates that list (either thru sha-1 or through just going by filename) + * sha1 route: + * have array where you store every sha1 of files (loop thru all file) + * if during your loop, you notice sha1 is already in array, disregard file + * filename route: + * have array with all files (maybe arrays in arrays containing folders) + * loop thru all those, store filename (minus folder name) in a new array + * if the name is already in array, remove it from the other array + * OR store them in the new array (with folder somewhere else, somehow (maybe just as an extra array so u can. folder[5] + filename[5] or smth) +* shuffles list +* measures up to ~8 hours of music +* converts into .opus files + * either as one mega-playlist-file + * this would work very nicely with pocketchip ipod shuffle app ur gonna make + * also easy to import to iphone + * or as a lot of files to just drop on a player +* if mega-playlist-file route, save a txt of when each song plays, so you can refer to that back later + * in the ipod shuffle app, also have a "note" button that just. saves it. yk diff --git a/peegen4k.py b/peegen4k.py new file mode 100644 index 0000000..2738608 --- /dev/null +++ b/peegen4k.py @@ -0,0 +1,163 @@ +import argparse, os, hashlib, random, mutagen, progressbar, ffmpeg, time + +# 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") \ No newline at end of file diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..0ac106c --- /dev/null +++ b/readme.md @@ -0,0 +1,20 @@ +# peeGen4K + +also known as "playlist generator" + +usage: + +`peegen4k.py inputfolder outputfile` + +also try: + +`peegen4k.py -h` because there's like a few options + +generates you a 8-hour playlist of random music files. +useful for pretending you're a radio station, but you dont have enough storage space to just dump your entire music collection and press shuffle. + +requirements: + +* python (wow!) +* ffmpeg (python library and the program itself) +* some banger tunes \ No newline at end of file diff --git a/todo.txt b/todo.txt new file mode 100644 index 0000000..618e476 --- /dev/null +++ b/todo.txt @@ -0,0 +1,6 @@ +upcoming features + +[ ] scan multiple folders ! (basically, excluding folders, but in reverse) +[ ] log file (for onefile, save when all songs play. so you can come back and see "ooh this sogn a banger i wonder what it called" l8r) +[ ] replaygain-type stuff?!?!?! +[ ] strip id3 \ No newline at end of file