2020-09-30 16:38:41 +00:00
|
|
|
import os
|
|
|
|
from mutagen.easyid3 import EasyID3
|
2020-09-30 19:28:32 +00:00
|
|
|
from mutagen.id3 import ID3, APIC, TIT2, TPE1, TALB
|
2020-09-30 16:38:41 +00:00
|
|
|
import getopt
|
|
|
|
import sys
|
|
|
|
import shutil
|
|
|
|
|
|
|
|
total_proc = 0
|
|
|
|
total_succ = 0
|
|
|
|
total_error = 0
|
|
|
|
total_cover = 0
|
|
|
|
|
|
|
|
input_dir = None
|
|
|
|
output_dir = None
|
|
|
|
cover_dir = None
|
|
|
|
cover_arts = []
|
|
|
|
|
2020-09-30 19:28:32 +00:00
|
|
|
UNKNOWN_ARTIST_NAME = "Unknown Artist"
|
2020-09-30 16:38:41 +00:00
|
|
|
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
|
|
|
|
|
|
|
|
|
2020-09-30 19:28:32 +00:00
|
|
|
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
|
|
|
|
|
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)
|
|
|
|
with open(f,'rb') as img:
|
|
|
|
cover_arts.append((artist, album, title, img.read(), os.path.basename(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
|
|
|
|
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)
|
|
|
|
|
|
|
|
# id3 = ID3(f)
|
|
|
|
|
|
|
|
# album = str(id3.get("TALB") if id3.get("TALB") != None else "")
|
2020-09-30 16:38:41 +00:00
|
|
|
if album == "":
|
|
|
|
album = UNKNOWN_ALBUM_NAME
|
|
|
|
|
2020-09-30 19:28:32 +00:00
|
|
|
# artist = str(id3.get("TPE1") if id3.get("TPE1") != None else "")
|
2020-09-30 16:38:41 +00:00
|
|
|
if artist == "":
|
|
|
|
artist = UNKNOWN_ARTIST_NAME
|
|
|
|
|
2020-09-30 19:28:32 +00:00
|
|
|
# title = str(id3.get("TIT2") if id3.get("TIT2") != None else "")
|
|
|
|
|
|
|
|
if title == "":
|
2020-09-30 16:38:41 +00:00
|
|
|
raise Exception("No title")
|
|
|
|
|
|
|
|
print(" Artist: " + artist + " Album: " + album + " Title: " + title)
|
|
|
|
|
|
|
|
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))
|
2020-09-30 19:43:58 +00:00
|
|
|
try:
|
|
|
|
shutil.copyfile(f, target_f)
|
|
|
|
except shutil.SameFileError:
|
|
|
|
pass
|
2020-09-30 16:38:41 +00:00
|
|
|
print(" Copied file to - " + target_f)
|
|
|
|
|
|
|
|
cover_idx = find_best_cover(artist, album, title)
|
2020-09-30 19:28:32 +00:00
|
|
|
|
|
|
|
# write id3 tags of the target file
|
|
|
|
id3 = ID3(target_f)
|
|
|
|
# write title
|
|
|
|
id3.delall("TIT2")
|
|
|
|
id3.add(TIT2(text = title))
|
|
|
|
# write album
|
|
|
|
id3.delall("TALB")
|
|
|
|
id3.add(TALB(text = album))
|
|
|
|
# write artist
|
|
|
|
id3.delall("TPE1")
|
|
|
|
id3.add(TPE1(text = artist))
|
2020-09-30 16:38:41 +00:00
|
|
|
|
|
|
|
if cover_idx >= 0:
|
|
|
|
# install cover
|
|
|
|
id3.delall("APIC")
|
|
|
|
id3.add(APIC(3, "image/jpeg", 3, "Front cover", cover_arts[cover_idx][3]))
|
|
|
|
total_cover += 1
|
|
|
|
print(" Written cover art - " + cover_arts[cover_idx][4])
|
|
|
|
|
2020-09-30 19:28:32 +00:00
|
|
|
id3.save(v2_version=3)
|
|
|
|
|
2020-09-30 16:38:41 +00:00
|
|
|
total_succ += 1
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
total_error += 1
|
|
|
|
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
|
|
|
|
|
|
|
|
try:
|
|
|
|
opts , _ = getopt.getopt(sys.argv[1:], "hi:o:c:")
|
|
|
|
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
|
|
|
|
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 - Total Files: " + str(total_proc) + " Total Errors: " + str(total_error) + " Total Covers: " + str(total_cover))
|
|
|
|
|
|
|
|
main()
|