diff --git a/mobject.py b/mobject.py new file mode 100644 index 0000000..39953af --- /dev/null +++ b/mobject.py @@ -0,0 +1,140 @@ +from mutagen.id3 import ID3, APIC, TIT2, TPE1, TALB, Encoding, PictureType +from mutagen.flac import FLAC, Picture +from pathlib import Path +import io + +class MObject: + _file_bytes : bytes + _file_io : io.BytesIO + + def __init__(self, fb : bytes): + self._file_bytes = fb + self._file_io = io.BytesIO(fb) + return + + def get_bytes(self) -> bytes: + return self._file_io.getbuffer() + + def get_title(self) -> str: + raise Exception("unimplemented") + + def get_album(self) -> str: + raise Exception("unimplemented") + + def get_artist(self) -> str: + raise Exception("unimplemented") + + def get_cover(self) -> bytes: + raise Exception("unimplemented") + + def set_artist(self, val : str) -> None: + raise Exception("unimplemented") + + def set_album(self, val : str) -> None: + raise Exception("unimplemented") + + def set_title(self, val : str) -> None: + raise Exception("unimplemented") + + def set_cover(self, val : bytes, png : bool = False) -> None: + raise Exception("unimplemented") + +class MMP3Object(MObject): + _id3 : ID3 + def __init__(self, file : bytes): + super().__init__(self, file) + + self._id3 = ID3() + self._id3.load(self._file_io, translate = True) + return + + def get_title(self) -> str: + return self._id3.get("TIT2", default = TIT2(text = None)).text + + def set_title(self, val : str) -> None: + self._id3.delall("TIT2") + self._id3.add(TIT2(text = val)) + self._id3.save() + + def get_album(self) -> str: + return self._id3.get("TALB", default = TALB(text = None)).text + + def set_album(self, val : str) -> None: + self._id3.delall("TALB") + self._id3.add(TALB(text = val)) + self._id3.save() + + def get_artist(self) -> str: + return self._id3.get("TPE1", default = TPE1(text = None)).text + + def set_artist(self, val : str): + self._id3.delall("TPE1") + self._id3.add(TPE1(text = val)) + self._id3.save() + + def get_cover(self) -> bytes: + cover = self._id3.get("APIC", default=APIC(encoding = Encoding.UTF8, mime = "image/jpeg", type=PictureType.COVER_FRONT, desc=u"Cover", data = None)) + return cover.data + + def set_cover(self, val : bytes, png : bool = False) -> None: + self._id3.delall("APIC") + self._id3.add(APIC(encoding = Encoding.UTF8, mime = "image/png" if png else "image/jpeg", type=PictureType.COVER_FRONT, desc=u"Cover", data = val)) + self._id3.save() + + +class MFLACObject(MObject): + _flac : FLAC + def __init__(self, file : bytes): + super().__init__(self, file) + + self._flac = FLAC(self._file_io) + return + + def get_title(self) -> str: + return self._flac.get("TITLE", default = None) + + def set_title(self, val : str) -> None: + self._flac["TITLE"] = val + self._flac.save() + + def get_album(self) -> str: + return self._flac.get("ALBUM", default = None) + + def set_album(self, val : str) -> None: + self._flac["ALBUM"] = val + self._flac.save() + + def get_artist(self) -> str: + return self._flac.get("ARTIST", default = None) + + def set_artist(self, val : str): + self._flac["ARTIST"] = val + self._flac.save() + + def get_cover(self) -> bytes: + pics = self._flac.pictures + if len(pics) > 0: + return pics[0].data + else: + return None + + def set_cover(self, val : bytes, png : bool = False) -> None: + self._flac.clear_pictures() + pic = Picture() + pic.type = PictureType.COVER_FRONT + pic.mime = "image/png" if png else "image/jpeg" + pic.desc = "Cover" + pic.data = val + self._flac.add_picture(pic) + self._flac.save() + + +def create_mobject(filepath : str) -> MObject: + fp = Path(filepath) + with open(fp, "rb") as f: + buf = f.read() + if (fp.suffix == "mp3"): + return MMP3Object(buf) + if (fp.suffix == "flac"): + return MFLACObject(buf) + return None \ No newline at end of file diff --git a/musick.py b/musick.py index fec17eb..432275a 100644 --- a/musick.py +++ b/musick.py @@ -1,15 +1,14 @@ import os -import mutagen -from mutagen.easyid3 import EasyID3 -from mutagen.id3 import ID3, APIC, TIT2, TPE1, TALB import getopt import sys import shutil +import mobject + total_proc = 0 total_succ = 0 total_error = 0 -total_cover = 0 +total_missing_cover = 0 input_dir = None output_dir = None @@ -108,10 +107,8 @@ def usage(): -o output : output directory\n\ -c cover : cover arts page\n") -no_match = [] - def process_file(f: str, odir: str): - global total_cover + global total_missing_cover global total_succ global total_error global total_proc @@ -120,51 +117,41 @@ def process_file(f: str, odir: str): total_proc += 1 artist, album, title = parse_triple(f) - - # 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 title == "": raise Exception("No title") - print(" Identified artist: " + artist + " album: " + album + " title: " + 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): + cover_idx = find_best_cover(artist, album, title) + if (cover_idx >= 0): + mobj.set_cover(cover_arts[cover_idx][3]) + print(" Cover: -> ") + else: + total_missing_cover += 1 + print(" Cover: -> ") + else: + print(" Cover: ") 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)) - try: - shutil.copyfile(f, target_f) - except shutil.SameFileError: - pass - print(" Copied file to: " + target_f) - - cover_idx = find_best_cover(artist, album, title) - - audio = mutagen.File(f) - print(f" File info: {audio.info.pprint()}") - audio["title"] = title - audio["artist"] = artist - audio["album"] = album - audio.save() - - # 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(f" Written metadata for {f} with cover art - " + cover_arts[cover_idx][4]) - # else: - # no_match.append(target_f) + with open(target_f, "wb") as tf: + tf.write(mobj.get_bytes()) + print(" Written file to: " + target_f) total_succ += 1 @@ -218,8 +205,6 @@ def main(): 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)) - for each in no_match: - print("No match: " + each) + print("\nSummary - Total Files: " + str(total_proc) + " Total Errors: " + str(total_error) + " Total Missing Covers: " + str(total_missing_cover)) main()