initial commit
BIN
arts/Assassin's Creed OST.jpg
Normal file
After Width: | Height: | Size: 893 KiB |
BIN
arts/Bon Jovi.jpg
Normal file
After Width: | Height: | Size: 8.7 KiB |
BIN
arts/Borderlands OST.jpg
Normal file
After Width: | Height: | Size: 374 KiB |
BIN
arts/Command and Conquer OST.jpg
Normal file
After Width: | Height: | Size: 354 KiB |
BIN
arts/Counter Strike OST.jpg
Normal file
After Width: | Height: | Size: 209 KiB |
BIN
arts/Dota OST.jpg
Normal file
After Width: | Height: | Size: 258 KiB |
BIN
arts/Dynasty Warriors OST.jpg
Normal file
After Width: | Height: | Size: 197 KiB |
BIN
arts/Eminem.jpg
Normal file
After Width: | Height: | Size: 186 KiB |
BIN
arts/Final Fantasy OST.jpg
Normal file
After Width: | Height: | Size: 3.3 MiB |
BIN
arts/Flying Steps.jpg
Normal file
After Width: | Height: | Size: 114 KiB |
BIN
arts/Fort Minor.jpg
Normal file
After Width: | Height: | Size: 467 KiB |
BIN
arts/Geoplex.jpg
Normal file
After Width: | Height: | Size: 34 KiB |
BIN
arts/Granblue Fantasy OST.jpg
Normal file
After Width: | Height: | Size: 842 KiB |
BIN
arts/Hexyz Force OST.jpg
Normal file
After Width: | Height: | Size: 60 KiB |
BIN
arts/Immediate Music.jpg
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
arts/Linkin Park.jpg
Normal file
After Width: | Height: | Size: 500 KiB |
BIN
arts/Metal Slug OST.jpg
Normal file
After Width: | Height: | Size: 308 KiB |
BIN
arts/Michael Jackson.jpg
Normal file
After Width: | Height: | Size: 554 KiB |
BIN
arts/Music Instructor.jpg
Normal file
After Width: | Height: | Size: 114 KiB |
BIN
arts/Nightwish.jpg
Normal file
After Width: | Height: | Size: 90 KiB |
BIN
arts/Portal OST.jpg
Normal file
After Width: | Height: | Size: 226 KiB |
BIN
arts/Radiant Historia OST.jpg
Normal file
After Width: | Height: | Size: 1.0 MiB |
BIN
arts/Rammstein.jpg
Normal file
After Width: | Height: | Size: 120 KiB |
BIN
arts/Touhou OST.jpg
Normal file
After Width: | Height: | Size: 2.9 MiB |
BIN
arts/Two Steps From Hell.jpg
Normal file
After Width: | Height: | Size: 68 KiB |
BIN
arts/Vae.jpg
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
arts/Xenoblade Chronicles OST.jpg
Normal file
After Width: | Height: | Size: 260 KiB |
BIN
arts/_.jpg
Normal file
After Width: | Height: | Size: 100 KiB |
BIN
arts/_Battlefield.jpg
Normal file
After Width: | Height: | Size: 457 KiB |
BIN
arts/_Digimon.jpg
Normal file
After Width: | Height: | Size: 275 KiB |
BIN
arts/_Dragonball Super.jpg
Normal file
After Width: | Height: | Size: 127 KiB |
BIN
arts/_Metal Gear Rising.jpg
Normal file
After Width: | Height: | Size: 164 KiB |
BIN
arts/_Monster Hunter 3G.jpg
Normal file
After Width: | Height: | Size: 5.2 MiB |
BIN
arts/_Monster Hunter 4G.jpg
Normal file
After Width: | Height: | Size: 669 KiB |
BIN
arts/_Monster Hunter Frontier.jpg
Normal file
After Width: | Height: | Size: 1.2 MiB |
BIN
arts/_Monster Hunter Stories.jpg
Normal file
After Width: | Height: | Size: 458 KiB |
BIN
arts/_Monster Hunter World.jpg
Normal file
After Width: | Height: | Size: 232 KiB |
BIN
arts/_Monster Hunter XX.jpg
Normal file
After Width: | Height: | Size: 1.3 MiB |
BIN
arts/_Nier Automata.jpg
Normal file
After Width: | Height: | Size: 1.1 MiB |
BIN
arts/_Pokemon BW2.jpg
Normal file
After Width: | Height: | Size: 324 KiB |
BIN
arts/_Pokemon ORAS.jpg
Normal file
After Width: | Height: | Size: 1.1 MiB |
BIN
arts/_Pokemon Origins.jpg
Normal file
After Width: | Height: | Size: 380 KiB |
BIN
arts/_Pokemon RSE.jpg
Normal file
After Width: | Height: | Size: 67 KiB |
BIN
arts/_Pokemon SM.jpg
Normal file
After Width: | Height: | Size: 224 KiB |
BIN
arts/_Pokemon SnS.jpg
Normal file
After Width: | Height: | Size: 385 KiB |
BIN
arts/_Pokemon Super Mystery Dungeon.jpg
Normal file
After Width: | Height: | Size: 259 KiB |
BIN
arts/_Pokemon USUM.jpg
Normal file
After Width: | Height: | Size: 224 KiB |
BIN
arts/_Slam Dunk.jpg
Normal file
After Width: | Height: | Size: 444 KiB |
BIN
arts/_Undertale.jpg
Normal file
After Width: | Height: | Size: 682 KiB |
BIN
arts/__Catastrophe.jpg
Normal file
After Width: | Height: | Size: 171 KiB |
BIN
arts/__Dragon Rises.jpg
Normal file
After Width: | Height: | Size: 138 KiB |
BIN
arts/__Gordon Montage.jpg
Normal file
After Width: | Height: | Size: 109 KiB |
BIN
arts/__True Survivor.jpg
Normal file
After Width: | Height: | Size: 143 KiB |
BIN
arts/周杰伦.jpg
Normal file
After Width: | Height: | Size: 66 KiB |
BIN
arts/林俊杰.jpg
Normal file
After Width: | Height: | Size: 204 KiB |
204
musick.py
Normal file
@ -0,0 +1,204 @@
|
||||
import os
|
||||
from mutagen.easyid3 import EasyID3
|
||||
from mutagen.id3 import ID3, APIC
|
||||
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 = []
|
||||
|
||||
UNKNOWN_ARTIST_NAME = "Unknown Artists"
|
||||
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 load_cover_arts():
|
||||
global cover_arts
|
||||
|
||||
if cover_dir == None:
|
||||
return
|
||||
|
||||
cdir = cover_dir
|
||||
files = os.listdir(cdir)
|
||||
for f in files:
|
||||
try:
|
||||
triple = os.path.splitext(f)[0].split("_")
|
||||
f = os.path.join(cover_dir, f)
|
||||
print("Processing cover art - " + f)
|
||||
artist = triple[0]
|
||||
album = ""
|
||||
title = ""
|
||||
if len(triple) >= 2:
|
||||
album = triple[1]
|
||||
if len(triple) >= 3:
|
||||
title = triple[2]
|
||||
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
|
||||
id3 = ID3(f)
|
||||
|
||||
album = str(id3.get("TALB") if id3.get("TALB") != None else "")
|
||||
if album == "":
|
||||
album = UNKNOWN_ALBUM_NAME
|
||||
|
||||
artist = str(id3.get("TPE1") if id3.get("TPE1") != None else "")
|
||||
if artist == "":
|
||||
artist = UNKNOWN_ARTIST_NAME
|
||||
|
||||
title = str(id3.get("TIT2") if id3.get("TIT2") != None else "")
|
||||
if artist == "":
|
||||
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))
|
||||
shutil.copyfile(f, target_f)
|
||||
print(" Copied file to - " + target_f)
|
||||
|
||||
cover_idx = find_best_cover(artist, album, title)
|
||||
|
||||
if cover_idx >= 0:
|
||||
# install cover
|
||||
id3 = ID3(target_f)
|
||||
id3.delall("APIC")
|
||||
id3.add(APIC(3, "image/jpeg", 3, "Front cover", cover_arts[cover_idx][3]))
|
||||
id3.save(v2_version=3)
|
||||
total_cover += 1
|
||||
print(" Written cover art - " + cover_arts[cover_idx][4])
|
||||
|
||||
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()
|