musick/musick.py

247 lines
6.9 KiB
Python
Raw Normal View History

2020-09-30 16:38:41 +00:00
import os
import getopt
import sys
2023-06-06 22:26:52 +00:00
import pathlib
2020-09-30 16:38:41 +00:00
2023-06-05 07:26:36 +00:00
import mobject
2020-09-30 16:38:41 +00:00
total_proc = 0
total_succ = 0
total_error = 0
2023-06-05 10:57:58 +00:00
total_cover_exists = 0
total_cover_written = 0
cover_missing = []
errors = []
2020-09-30 16:38:41 +00:00
input_dir = None
output_dir = None
cover_dir = None
2023-06-05 22:55:55 +00:00
overwrite_cover = False
2020-09-30 16:38:41 +00:00
cover_arts = []
WILDCARD_MATCH_SCORE = 1
ARTIST_MATCH_SCORE = 10
ALBUM_MATCH_SCORE = 100
TITLE_MATCH_SCORE = 1000
2023-10-27 18:13:36 +00:00
escape_list : list[tuple[str, str]] = [
2024-10-12 19:36:26 +00:00
("%..%" , ":"),
("%sl%" , "/")
2023-10-27 18:13:36 +00:00
]
def escape_str(input : str) -> str:
for tup in escape_list:
input = input.replace(tup[0], tup[1])
return input
2020-09-30 16:38:41 +00:00
# to get a > 0 score, each field either must match or the cover tuple is wildcare (None)
2023-06-06 22:26:52 +00:00
def calc_cover_score(artist: list[str], album: list[str], title: list[str], tup : tuple) -> int:
2020-09-30 16:38:41 +00:00
score = 0
if (tup[0] == artist):
score += ARTIST_MATCH_SCORE
2023-06-06 22:26:52 +00:00
elif (tup[0] == [""]):
2020-09-30 16:38:41 +00:00
score += WILDCARD_MATCH_SCORE
else:
# mismatched artist, discard
return 0
if (tup[1] == album):
score += ALBUM_MATCH_SCORE
2023-06-06 22:26:52 +00:00
elif (tup[1] == [""]):
2020-09-30 16:38:41 +00:00
score += WILDCARD_MATCH_SCORE
else:
# mismatched album, discard
return 0
if (tup[2] == title):
score += TITLE_MATCH_SCORE
2023-06-06 22:26:52 +00:00
elif (tup[2] == [""]):
2020-09-30 16:38:41 +00:00
score += WILDCARD_MATCH_SCORE
else:
# mismatched title, discard
return 0
return score
# returns the idx into cover_arts array.
# < 0 means no match
2023-06-06 22:26:52 +00:00
def find_best_cover(artist: list[str], album: list[str], title: list[str]) -> int:
2020-09-30 16:38:41 +00:00
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
2023-06-06 22:26:52 +00:00
def plus_str_to_list(s : str) -> list[str]:
ss : list[str] = s.split("+")
ret = []
for sss in ss:
ret.append(sss)
return ret
2020-09-30 16:38:41 +00:00
2023-10-27 18:13:36 +00:00
def parse_triple(f: str, escape : bool = True) -> tuple[list[str], list[str], list[str]]:
2020-09-30 19:28:32 +00:00
triple = os.path.splitext(os.path.basename(f))[0].split("_")
album = ""
title = ""
2023-10-27 18:13:36 +00:00
artist = plus_str_to_list(escape_str(triple[0]) if escape else triple[0])
2020-09-30 19:28:32 +00:00
if len(triple) >= 2:
2023-10-27 18:13:36 +00:00
album = plus_str_to_list(escape_str(triple[1]) if escape else triple[1])
2020-09-30 19:28:32 +00:00
if len(triple) >= 3:
2023-10-27 18:13:36 +00:00
title = plus_str_to_list(escape_str(triple[2]) if escape else triple[2])
2023-06-06 22:26:52 +00:00
return (artist, album, title)
2020-09-30 19:28:32 +00:00
2020-09-30 16:38:41 +00:00
def load_cover_arts():
global cover_arts
if cover_dir == None:
return
cdir = cover_dir
files = os.listdir(cdir)
for f in files:
try:
2020-09-30 19:28:32 +00:00
artist, album, title = parse_triple(f)
2020-09-30 16:38:41 +00:00
f = os.path.join(cover_dir, f)
print("Processing cover art - " + f)
2023-06-06 22:26:52 +00:00
fp = pathlib.Path(f)
if (fp.suffix.lower() == ".png"):
type = "PNG"
else:
type = "JPEG"
2020-09-30 16:38:41 +00:00
with open(f,'rb') as img:
2023-06-06 22:26:52 +00:00
cover_arts.append((artist, album, title, img.read(), type, f))
print(" Loading cover art - " + f + " Artist: " + str(artist) + " Album: " + str(album) + " Title: " + str(title) + " Type: " + type)
2020-09-30 16:38:41 +00:00
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):
2023-06-05 10:57:58 +00:00
global total_cover_exists
global total_cover_written
2020-09-30 16:38:41 +00:00
global total_succ
global total_error
global total_proc
try:
print("Processing file - " + f)
total_proc += 1
2020-09-30 19:28:32 +00:00
artist, album, title = parse_triple(f)
2023-10-27 18:13:36 +00:00
fartist, falbum, _ = parse_triple(f, escape=False)
2023-06-06 22:26:52 +00:00
if len(title) == 0:
2020-09-30 16:38:41 +00:00
raise Exception("No title")
2023-06-05 07:26:36 +00:00
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)
2024-02-28 19:11:45 +00:00
mobj.set_album_artist(artist)
2023-06-05 07:26:36 +00:00
mobj.set_album(album)
mobj.set_title(title)
2023-06-05 22:55:55 +00:00
if (mobj.get_cover() == None or overwrite_cover):
2023-06-05 07:26:36 +00:00
cover_idx = find_best_cover(artist, album, title)
if (cover_idx >= 0):
2023-06-06 22:26:52 +00:00
mobj.set_cover(cover_arts[cover_idx][3], cover_arts[cover_idx][4] == "PNG")
2023-06-05 22:55:55 +00:00
print(f" Cover: <{'None' if mobj.get_cover() == None else 'Exists'}> -> \"{cover_arts[cover_idx][5]}\"")
2023-06-05 10:57:58 +00:00
total_cover_written += 1
2023-06-05 07:26:36 +00:00
else:
2023-06-05 22:55:55 +00:00
print(f" Cover: <Missing>")
2023-06-05 10:57:58 +00:00
cover_missing.append(f)
2023-06-05 07:26:36 +00:00
else:
print(" Cover: <Exists>")
2023-06-05 10:57:58 +00:00
total_cover_exists += 1
2020-09-30 16:38:41 +00:00
2023-10-27 18:13:36 +00:00
target_dir = os.path.join(odir, "+".join(fartist), "+".join(falbum))
2020-09-30 16:38:41 +00:00
os.makedirs(target_dir, exist_ok = True)
target_f = os.path.join(target_dir, os.path.basename(f))
2023-06-05 07:26:36 +00:00
with open(target_f, "wb") as tf:
tf.write(mobj.get_bytes())
print(" Written file to: " + target_f)
2020-09-30 19:28:32 +00:00
2020-09-30 16:38:41 +00:00
total_succ += 1
except Exception as e:
total_error += 1
2023-06-05 10:57:58 +00:00
errors.append(f)
2020-09-30 16:38:41 +00:00
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
2023-06-05 22:55:55 +00:00
global overwrite_cover
2020-09-30 16:38:41 +00:00
try:
2023-06-05 22:55:55 +00:00
opts , _ = getopt.getopt(sys.argv[1:], "hi:o:c:f")
2020-09-30 16:38:41 +00:00
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
2023-06-05 22:55:55 +00:00
elif o == "-f":
overwrite_cover = True
2020-09-30 16:38:41 +00:00
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)
2023-06-05 10:57:58 +00:00
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}")
2020-09-30 16:38:41 +00:00
main()