a
This commit is contained in:
commit
8cb8c598ba
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
*.opus
|
||||||
|
.DS_Store
|
26
outline.md
Normal file
26
outline.md
Normal file
@ -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
|
163
peegen4k.py
Normal file
163
peegen4k.py
Normal file
@ -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")
|
20
readme.md
Normal file
20
readme.md
Normal file
@ -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
|
6
todo.txt
Normal file
6
todo.txt
Normal file
@ -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
|
Loading…
Reference in New Issue
Block a user