230 lines
6.1 KiB
Python
230 lines
6.1 KiB
Python
import os
|
|
import getopt
|
|
import sys
|
|
import shutil
|
|
|
|
import mobject
|
|
|
|
total_proc = 0
|
|
total_succ = 0
|
|
total_error = 0
|
|
total_cover_exists = 0
|
|
total_cover_written = 0
|
|
cover_missing = []
|
|
errors = []
|
|
|
|
input_dir = None
|
|
output_dir = None
|
|
cover_dir = None
|
|
overwrite_cover = False
|
|
cover_arts = []
|
|
|
|
UNKNOWN_ARTIST_NAME = "Unknown Artist"
|
|
UNKNOWN_ALBUM_NAME = "Unknown Album"
|
|
|
|
WILDCARD_MATCH_SCORE = 1
|
|
ARTIST_MATCH_SCORE = 10
|
|
ALBUM_MATCH_SCORE = 100
|
|
TITLE_MATCH_SCORE = 1000
|
|
|
|
# to get a > 0 score, each field either must match or the cover tuple is wildcare (None)
|
|
def calc_cover_score(artist: str, album: str, title: str, tup : tuple):
|
|
score = 0
|
|
|
|
if (tup[0] == artist):
|
|
score += ARTIST_MATCH_SCORE
|
|
elif (tup[0] == ""):
|
|
score += WILDCARD_MATCH_SCORE
|
|
else:
|
|
# mismatched artist, discard
|
|
return 0
|
|
|
|
if (tup[1] == album):
|
|
score += ALBUM_MATCH_SCORE
|
|
elif (tup[1] == ""):
|
|
score += WILDCARD_MATCH_SCORE
|
|
else:
|
|
# mismatched album, discard
|
|
return 0
|
|
|
|
if (tup[2] == title):
|
|
score += TITLE_MATCH_SCORE
|
|
elif (tup[2] == ""):
|
|
score += WILDCARD_MATCH_SCORE
|
|
else:
|
|
# mismatched title, discard
|
|
return 0
|
|
|
|
return score
|
|
|
|
# returns the idx into cover_arts array.
|
|
# < 0 means no match
|
|
def find_best_cover(artist: str, album: str, title: str):
|
|
best_idx = -1
|
|
best_score = 0
|
|
for i in range(0, len(cover_arts)):
|
|
score = calc_cover_score(artist, album, title, cover_arts[i])
|
|
if score > best_score:
|
|
best_idx = i
|
|
best_score = score
|
|
|
|
return best_idx
|
|
|
|
|
|
def parse_triple(f: str):
|
|
triple = os.path.splitext(os.path.basename(f))[0].split("_")
|
|
album = ""
|
|
title = ""
|
|
artist = triple[0]
|
|
if len(triple) >= 2:
|
|
album = triple[1]
|
|
if len(triple) >= 3:
|
|
title = triple[2]
|
|
return artist, album, title
|
|
|
|
def load_cover_arts():
|
|
global cover_arts
|
|
|
|
if cover_dir == None:
|
|
return
|
|
|
|
cdir = cover_dir
|
|
files = os.listdir(cdir)
|
|
for f in files:
|
|
try:
|
|
artist, album, title = parse_triple(f)
|
|
f = os.path.join(cover_dir, f)
|
|
print("Processing cover art - " + f)
|
|
with open(f,'rb') as img:
|
|
cover_arts.append((artist, album, title, img.read(), os.path.basename(f), f))
|
|
print(" Loading cover art - " + f + " Artist: " + artist + " Album: " + album + " Title: " + title)
|
|
except Exception as e:
|
|
print(" Skipping due to Exception - " + str(e))
|
|
print("")
|
|
|
|
|
|
def usage():
|
|
print("Usage: python musik.py [options]\n\n\
|
|
options:\n\
|
|
-h : show usage.\n\
|
|
-i input : input directory\n\
|
|
-o output : output directory\n\
|
|
-c cover : cover arts page\n")
|
|
|
|
def process_file(f: str, odir: str):
|
|
global total_cover_exists
|
|
global total_cover_written
|
|
global total_succ
|
|
global total_error
|
|
global total_proc
|
|
try:
|
|
print("Processing file - " + f)
|
|
total_proc += 1
|
|
|
|
artist, album, title = parse_triple(f)
|
|
if album == "":
|
|
album = UNKNOWN_ALBUM_NAME
|
|
if artist == "":
|
|
artist = UNKNOWN_ARTIST_NAME
|
|
if title == "":
|
|
raise Exception("No title")
|
|
|
|
mobj = mobject.create_mobject(f)
|
|
|
|
print(f" Artist: \"{mobj.get_artist()}\" -> \"{artist}\"\n" +
|
|
f" Album: \"{mobj.get_album()}\" -> \"{album}\"\n" +
|
|
f" Title: \"{mobj.get_title()}\" -> \"{title}\"")
|
|
|
|
mobj.set_artist(artist)
|
|
mobj.set_album(album)
|
|
mobj.set_title(title)
|
|
|
|
if (mobj.get_cover() == None or overwrite_cover):
|
|
cover_idx = find_best_cover(artist, album, title)
|
|
if (cover_idx >= 0):
|
|
mobj.set_cover(cover_arts[cover_idx][3])
|
|
print(f" Cover: <{'None' if mobj.get_cover() == None else 'Exists'}> -> \"{cover_arts[cover_idx][5]}\"")
|
|
total_cover_written += 1
|
|
else:
|
|
print(f" Cover: <Missing>")
|
|
cover_missing.append(f)
|
|
else:
|
|
print(" Cover: <Exists>")
|
|
total_cover_exists += 1
|
|
|
|
target_dir = os.path.join(odir, artist, album)
|
|
os.makedirs(target_dir, exist_ok = True)
|
|
|
|
target_f = os.path.join(target_dir, os.path.basename(f))
|
|
with open(target_f, "wb") as tf:
|
|
tf.write(mobj.get_bytes())
|
|
print(" Written file to: " + target_f)
|
|
|
|
total_succ += 1
|
|
|
|
except Exception as e:
|
|
total_error += 1
|
|
errors.append(f)
|
|
print(" Skipping due to exception - %s" % str(e))
|
|
|
|
print("")
|
|
|
|
def process_directory(idir: str, odir: str):
|
|
print("Processing directory - " + idir)
|
|
files = os.listdir(idir)
|
|
for f in files:
|
|
f = os.path.join(idir, f)
|
|
if os.path.isdir(f):
|
|
process_directory(f, odir)
|
|
else:
|
|
process_file(f, odir)
|
|
|
|
|
|
def main():
|
|
global input_dir
|
|
global output_dir
|
|
global cover_dir
|
|
global overwrite_cover
|
|
|
|
try:
|
|
opts , _ = getopt.getopt(sys.argv[1:], "hi:o:c:f")
|
|
except getopt.GetoptError as err:
|
|
print(str(err))
|
|
usage()
|
|
sys.exit(1)
|
|
|
|
for o, a in opts:
|
|
if o == "-h":
|
|
usage()
|
|
sys.exit(0)
|
|
elif o == "-i":
|
|
input_dir = a
|
|
elif o == "-o":
|
|
output_dir = a
|
|
elif o == "-c":
|
|
cover_dir = a
|
|
elif o == "-f":
|
|
overwrite_cover = True
|
|
else:
|
|
print("Unrecognized option: " + o)
|
|
sys.exit(1)
|
|
|
|
if input_dir == None or output_dir == None:
|
|
print("Must specify both -i and -o")
|
|
sys.exit(1)
|
|
|
|
load_cover_arts()
|
|
process_directory(input_dir, output_dir)
|
|
|
|
print("\nSummary - Files: " + str(total_proc) + ". Processed: " + str(total_succ) + ". Cover Exists: " + str(total_cover_exists) + ". Cover Written: " + str(total_cover_written))
|
|
if (len(errors) > 0):
|
|
print(f"Errors: {len(errors)}")
|
|
for f in errors:
|
|
print(f" {f}")
|
|
if (len(cover_missing) > 0):
|
|
print(f"Cover Missing: {len(cover_missing)}")
|
|
for f in cover_missing:
|
|
print(f" {f}")
|
|
|
|
main()
|