def _copy_music_changed(self): for x in self.music_changed: ext_left = self.music_left[x]['ext'] full_path_left = os.path.join(self.base_path_left, f'{x}{ext_left}') if ext_left.lower() == '.flac': music_thing_left = flac.FLAC(full_path_left) tags_left = music_thing_left.tags.as_dict() elif ext_left.lower() == '.mp3': music_thing_left = mp3.MP3(full_path_left) tags_left = id3_tags_as_dict(music_thing_left.tags) else: raise Exception('What is this thing?') ext_right = self.music_right[x]['ext'] assert ext_right == '.mp3', 'Target file is not .mp3' full_path_right = os.path.join(self.base_path_right, f'{x}{ext_right}') music_thing_right = mp3.MP3(full_path_right) tags_right = id3_tags_as_dict(music_thing_right.tags) if tags_left != tags_right: print_if_not_silent(f'copying tags {full_path_left}') copy_tag_dict_to_mp3(music_thing_right, tags_left) else: if ext_left.lower() == '.flac': print_if_not_silent(f'encoding {full_path_left} to mp3') flac_to_mp3(full_path_left, full_path_right, tag_dict=tags_left) else: print_if_not_silent(f'copying {full_path_left} to right') shutil.copy2(full_path_left, full_path_right)
def load_from_filepath(self, filepath: str): self.filepath = filepath self._filesize = os.stat(filepath).st_size file = File(filepath) mime = file.mime tags: Optional[TagsInfo] = None info: InfoBlock = InfoBlock() if 'audio/mp3' in mime: mp3_data = mp3.EasyMP3(filepath) tags = cast(TagsInfo, mp3_data) info = cast(InfoBlock, mp3_data.info) elif 'audio/flac' in mime: flac_data = flac.FLAC(filepath) tags = cast(TagsInfo, flac_data) info = cast(InfoBlock, flac_data.info) else: raise TypeError self._bitrate = info.bitrate tag_name = None try: tag_name = 'title' self._tags.titles = tags[tag_name] tag_name = 'album' self._tags.albums = tags[tag_name] self.title = self._tags.titles[0] except KeyError as err: self._logger.warning('Not found tag "{0}" in file "{1}"'.format( tag_name, filepath)) self._logger.debug(err) self.is_found = True self._filename = os.path.basename(filepath)
def processflac(filename): '''Reads metadata from FLAC files''' f = flac.FLAC(filename) title, anime, role, rolequal = part(f.get('title', _blank)[0]) label = f.get('description', _blank)[0] if not label: label = f.get('subtitle', _blank)[0] album = f.get('album', _blank)[0] artist = f.get('artist', _blank)[0] fn_artist = artist if artist else 'Unknown Artist' id = random_id() return [ metadata( ID=id, OriginalFileName=filename, Filename=canonicalFileName(id, artist, title, 'flac'), Tracktitle=title, Album=album, Length=int(f.info.length * 1000), Anime=anime, Role=role, Rolequalifier=rolequal, Artist=artist, Composer=f.get('composer', _blank)[0], Label=label, InMyriad="NO", Dateadded=datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M")) ]
def identify_filetype(file): """Identify the given file as either MP3 or FLAC and return a Mutagen object.""" if file.endswith('.mp3'): audio_file = mp3.MP3(file, ID3=easyid3.EasyID3) elif file.endswith('.flac'): audio_file = flac.FLAC(file) return audio_file
def tag_scanner(music_path): if music_path.suffix == ".aiff" or music_path.suffix == ".aif": return aiff.AIFF(music_path) elif music_path.suffix == ".flac": return flac.FLAC(music_path) elif music_path.suffix == ".mp3": return easyid3.EasyID3(music_path) else: print("Invalid FileType")
def acquireFlacInfo(src): mu = flac.FLAC(src) trackNum = mu.get("TRCK").text[0] # 得到音轨号 performer = mu.get("TPE1").text[0] # 得到表演者 songName = songTitleFormat(mu.get("TIT2").text[0]) # 得到歌曲名称 albumName = mu.get("TALB").text[0] if int(trackNum) < 10: trackNum = '0' + trackNum # print(trackNum, performer, songName, albumName) return trackNum, performer, songName, albumName
def flac_art(self, flac_file): song = mflac.FLAC(flac_file) pic = mflac.Picture() pic.data = self.art_data pic.type = 3 pic.mime = 'image/jpeg' song.add_picture(pic) song.save()
def tags_flac(name, infer=True): try: tags = flac.FLAC(name) except (mutagen.flac.FLACNoHeaderError, mutagen.flac.error): if infer: uprint( 'WARN: Corrupted or mistagged, inferring artist and album: %s ...' % name, end=' ') return infer_album_artist(name) return None if not tags: return None, None return tags['albumartist'][0] if 'albumartist' in tags.keys() else tags['artist'][0] if 'artist' in tags.keys() \ else None, tags['album'][0] if 'album' in tags.keys() else None
def extract(path): if open(path, 'rb').read(4) == binascii.a2b_hex('664C6143'): audio = flac.FLAC(path) identifier = audio['description'] else: audio = mp3.MP3(path) identifier = [ text for item in audio.tags.getall('COMM') for text in item.text ] identifier = max(identifier, key=len) identifier = base64.b64decode(identifier[22:]) cryptor = AES.new(key, AES.MODE_ECB) meta = unpad(cryptor.decrypt(identifier), 16).decode('utf8') return json.loads(meta[6:])
async def detecter(self, songdir): #リストアップ files = os.listdir(songdir) files_file = [ f for f in files if os.path.isfile(os.path.join(songdir, f)) ] #osuファイル検出 osu_detect = [l for l in files_file if l.endswith('.osu')] osu_osup = os.path.join(songdir, osu_detect[0]) osu_osuo = open(osu_osup, encoding='utf_8') osu_raw = osu_osuo.readlines() osu_osuo.close() AFn = None Tit = None UTit = None dosz_id = None for line in osu_raw: line = line.replace('\n', '') if line.startswith("AudioFilename:"): AFn = line[15:] continue elif line.startswith("Title:"): Tit = line[6:] continue elif line.startswith("TitleUnicode:"): UTit = line[13:] break elif line.startswith("[Difficulty]"): break if UTit: Tit = UTit AudioFname = os.path.join(songdir, AFn) if AudioFname.endswith(".mp3") or AudioFname.endswith(".MP3"): af = MP3(AudioFname) elif AudioFname.endswith(".ogg") or AudioFname.endswith(".OGG"): af = oggvorbis.OggVorbis(AudioFname) elif AudioFname.endswith(".m4a") or AudioFname.endswith(".M4A"): af = MP4(AudioFname) elif AudioFname.endswith(".flac") or AudioFname.endswith(".FLAC"): af = flac.FLAC(AudioFname) duration = int(af.info.length) + 1 dosz_idd = os.path.dirname(songdir) dosz_ids = songdir.replace(dosz_idd, '').split(' ')[0] dosz_id = dosz_ids.replace('\\', '') print("検出ID:{}".format(dosz_id)) return Tit, AudioFname, duration, dosz_id
def check(file,parent_folder_path): fl = os.path.join(parent_folder_path, file) if file.endswith('.flac'): f = flac.FLAC(fl).info rate = f.sample_rate else: f = wave.open(fl) rate = f.getframerate() print(rate) if(16000 <= rate <= 48000): return '🙂' else: return '�'
def flac_to_mp3(input_path, output_path, tag_dict=None): """ Converts flac to mp3 and copies tags. :param input_path: str. :param output_path: str. :param tag_dict: flac_like dict {'tag_name': ['tag_value', ...]} """ flac_options = '-d -s --force-raw-format --endian=little --sign=signed -c' lame_options = '-r --quiet --add-id3v2 --noreplaygain' cp1 = subprocess.run(f'"{flac_prog}" {flac_options} "{input_path}"', capture_output=True) subprocess.run(f'"{lame_prog}" -{lame_quality} {lame_options} "-" "{output_path}"', input=cp1.stdout, capture_output=False) mp3_thing = mp3.MP3(output_path) if not tag_dict: tag_dict = flac.FLAC(input_path).tags.as_dict() copy_tag_dict_to_mp3(mp3_thing, tag_dict)
def __init__(self, filename): from mutagen import flac self.obj = flac.FLAC(filename) self.map = { 'desc': 'desc', 'description': 'desc', 'album': 'album', 'comment': 'comment', 'artist': 'artist', 'title': 'title', 'tracknumber': 'tracknumber', 'discnumber': 'discnumber', 'albumartist': 'albumartist', 'composer': 'composer', 'disccount': 'disccount', 'tracktotal': 'tracktotal', 'date': 'date', 'genre': 'genre', 'website': 'www'}
def metadata(self): #dictionary track_data = {} # default name track_data['title'] = self.path[self.path.rfind('/') + 1:self.path.rfind('.')] track_data['album'] = None track_data['genre'] = None track_data['artist'] = None #detect the current file type file_type = self.path[self.path.rfind('.'):] # handle FLAC files if file_type == ".flac": metadata = flac.FLAC(self.path) # print metadata try: track_data['title'] = metadata['title'][0] track_data['album'] = metadata['album'][0] track_data['genre'] = metadata['genre'][0] track_data['artist'] = metadata['artist'][0] except: pass #handle MP3 files if file_type == ".mp3": metadata = mp3.MP3(self.path) # print metadata try: if (metadata.has_key('TIT2')): track_data['title'] = metadata['TIT2'].text[0] if (metadata.has_key('TALB')): track_data['album'] = metadata['TALB'].text[0] if (metadata.has_key('TCON')): track_data['genre'] = metadata['TCON'].text[0] if (metadata.has_key('TPE2')): track_data['artist'] = metadata['TPE2'].text[0] except: pass return track_data
def flacinfo(self): """ FLACの曲情報を取得 """ self.tags = flac.FLAC(self.filepath) # 各項目取得 # キーが存在しなかった場合: 半角空白に置き換え self.title = self.tags.get('TITLE', ' ')[0].strip() self.album = self.tags.get('ALBUM', ' ')[0].strip() self.artist = self.tags.get('ARTIST', ' ')[0].strip() self.genre = self.tags.get('GENRE', ' ')[0].strip() self.comment = self.tags.get('COMMENT', ' ')[0].strip() artworks = self.tags.pictures artwork = None for artwork in artworks: # 抽出(最後に登録されている画像のみ) pass if artwork: # アートワーク画像が存在するか self.artwork = artwork.data # type: bytes else: self.artwork = None
def flac_tag(self): song = mflac.FLAC(self.audio_file) info_dict = { 'album': 'ALBUM', 'album_artist': 'ALBUMARTIST', 'artist': 'ARTIST', 'date': 'DATE', 'label': 'LABEL', 'genre': 'GENRE', 'title': 'TITLE', 'track_number': 'TRACKNUMBER', 'total_tracks': 'TOTALTRACKS', 'disc_number': 'DISCNUMBER', 'total_discs': 'TOTALDISCS' } metadata_dictionary = self.create_tag_dict(song, info_dict) return metadata_dictionary
def flac_refresh(self, track_path: str) -> bool: try: audio = flac.FLAC(track_path) tag_dict = {} for key, value in audio.tags: tag_dict[key.lower()] = value artist, title = tag_dict['artist'], tag_dict['title'] title = title_prettify(title) lyrics = self.get_lyrics(artist, title) if lyrics: audio.tags['Lyrics'] = lyrics audio.save() return True except Exception as e: logging.warning(f"EXCEPTION at {track_path}: {e}") return False
def insert_flac(abs_audio_f): cover = cut_suffix(abs_audio_f) + '.jpg' mime = u"image/jpeg" if not os.path.exists(cover): cover = cut_suffix(abs_audio_f) + '.png' mime = u"image/png" if not os.path.exists(cover): # print('Cover for [' + abs_audio_f + '] not found.') return audio = flac.FLAC(abs_audio_f) audio.clear_pictures() with open(cover, 'rb') as img_f: pic = flac.Picture() pic.data = img_f.read() pic.type = id3.PictureType.COVER_FRONT pic.mime = mime # pic.width = 500 # pic.height = 500 audio.add_picture(pic) try: audio.save() except MutagenError: MutagenErrors.append(abs_audio_f)
def vorbis_tag(self, a_file): """ This function takes a metadata dictionary and uses Mutagen to apply it to a FLAC or OGG Vorbis file. """ title, extension = os.path.splitext(a_file) if extension == '.flac': song = mflac.FLAC(a_file) elif extension == '.ogg': song = mogg.OggVorbis(a_file) else: print("%s format not permitted by flac_paste" % extension) # Remove any pre-existing tags song.delete() info_dict = self.info_dict transfer_dictionary = { 'ALBUM': info_dict['album'], 'ALBUMARTIST': info_dict['album_artist'], 'ARTIST': info_dict['artist'], 'DATE': info_dict['date'][:4], 'LABEL': info_dict['label'], 'GENRE': info_dict['genre'], 'TITLE': self.track_title, 'TRACKNUMBER': self.track_number.zfill(2), 'TOTALTRACKS': str(info_dict['total_tracks']).zfill(2), 'DISCNUMBER': info_dict['disc'], 'TOTALDISCS': info_dict['total_discs'] } for tag in transfer_dictionary: song[tag] = transfer_dictionary[tag] song.save()
def mark(path, song, id=None): def streamify(file): with file: return file.read() def embed(item, content, type): item.encoding = 0 item.type = type item.mime = 'image/png' if content[0:4] == binascii.a2b_hex( '89504E47') else 'image/jpeg' item.data = content format = 'flac' if open( path, 'rb').read(4) == binascii.a2b_hex('664C6143') else 'mp3' audio = mp3.EasyMP3(path) if format == 'mp3' else flac.FLAC(path) meta = { 'album': song['album']['name'], 'albumId': song['album']['id'], 'albumPic': song['album']['picUrl'], 'albumPicDocId': str(song['album']['pic'] if 'pic' in song['album'] else re. search(r'/(\d+)\.\w+$', song['album']['picUrl']).group(1)), 'alias': song['alias'] if 'alias' in song else [], 'artist': [[artist['name'], artist['id']] for artist in song['artists']], 'musicId': id if id else song['id'], 'musicName': song['name'] if 'name' in song else audio['title'][0], 'mvId': song['mvid'] if 'mvid' in song else 0, 'transNames': [], 'format': format, 'bitrate': audio.info.bitrate, 'duration': int(audio.info.length * 1000), 'mp3DocId': hashlib.md5(streamify(open(path, 'rb'))).hexdigest() } cryptor = AES.new(key, AES.MODE_ECB) identifier = 'music:' + json.dumps(meta) identifier = '163 key(Don\'t modify):' + base64.b64encode( cryptor.encrypt(pad(identifier.encode('utf8'), 16))).decode('utf8') audio.delete() audio['title'] = meta['musicName'] audio['album'] = meta['album'] audio['artist'] = '/'.join([artist[0] for artist in meta['artist']]) if format == 'flac': audio['description'] = identifier else: audio.tags.RegisterTextKey('comment', 'COMM') audio['comment'] = identifier audio.save() data = requests.get(meta['albumPic'] + '?param=300y300').content if format == 'flac': audio = flac.FLAC(path) image = flac.Picture() embed(image, data, 3) audio.clear_pictures() audio.add_picture(image) elif format == 'mp3': audio = mp3.MP3(path) image = id3.APIC() embed(image, data, 6) audio.tags.add(image) audio.save()
def __update_tag(self, download_dir, audio_file, image_file, song_title=None, album_title=None, album_artist=None, album_composer=None, track_number=-1, process_index=-1, process_total=-1): """ The function that update audio metadata for each song. :param str download_dir: Download directory :param str audio_file: Path to audio file :param str image_file: Path to image file :param str song_title: Song title :param str album_title: Album title to be saved in metadata :param str album_artist: Album artist to be saved in metadata :param str album_composer: Album composer to be saved in metadata :param int track_number: track number to be saved in metadata :param int process_index: Current process index displayed in log message :param int process_total: Total number of process displayed in log message """ if audio_file is None: logger.warning('[Process:{}/{}][Track:{}] Could not update metadata because there is no data found on the playlist. The video may be private or deleted.'.format(process_index, process_total, track_number)) return if process_index > 0 and process_total > 0: if track_number > 0: log_prefix = '[Process:{}/{}][Track:{}]'.format(process_index, process_total, track_number) else: log_prefix = '[Process:{}/{}]'.format(process_index, process_total) else: log_prefix = '' audio_filename = os.path.basename(audio_file) try: # Validate audio data if not os.path.isfile(audio_file): raise FileNotFoundError(errno.ENOENT, os.strerror(errno.ENOENT), audio_file) audio_mime_type = mimetypes.guess_type(audio_file) if contains_at_least(audio_mime_type, ['audio/x-mp4', 'audio/x-m4a', 'audio/mp4a-latm']): # For more info about mp4 tag is available at # https://github.com/quodlibet/mutagen/blob/cf399dc58940fb1356f672809d763be9e2af0033/mutagen/mp4/__init__.py # http://atomicparsley.sourceforge.net/mpeg-4files.html mp4_data = mp4.MP4(audio_file) # Track Number if not self.no_track_number and track_number > 0: mp4_data['trkn'] = [(track_number, 0)] # Cover image if not self.no_artwork: image_data = self.__get_tag_image(image_file, audio_mime_type) if image_data: mp4_data['covr'] = [image_data] # Album title if not self.no_album_title and album_title is not None: mp4_data['\xa9alb'] = album_title # Album artist if not self.no_album_artist and album_artist is not None: mp4_data['aART'] = album_artist # Composer if not self.no_composer and album_composer is not None: mp4_data['\xa9wrt'] = album_composer # Part of compilation if not self.no_compilation: mp4_data['cpil'] = True # Save mp4_data.save() elif contains_at_least(audio_mime_type, ['audio/x-mp3', 'audio/mpeg']): # For more info about ID3v2 tag is available at # https://github.com/quodlibet/mutagen/blob/4a5d7d17f1a611280cc52d229aa70b77ca3c55dd/mutagen/id3/_frames.py # https://help.mp3tag.de/main_tags.html mp3_data = id3.ID3(audio_file) # Cover image if not self.no_artwork: image_data = self.__get_tag_image(image_file, audio_mime_type) if image_data: mp3_data['APIC'] = image_data # Track number if not self.no_track_number and track_number > 0: mp3_data.add(id3.TRCK(encoding=3, text=['{}/{}'.format(track_number, 0)])) # Album title if not self.no_album_title and album_title is not None: mp3_data["TALB"] = id3.TALB(encoding=0, text=album_title) # Album artist if not self.no_album_artist and album_artist is not None: mp3_data["TPE2"] = id3.TPE2(encoding=0, text=album_artist) # Composer if not self.no_composer and album_composer is not None: mp3_data["TCOM"] = id3.TCOM(encoding=0, text=album_composer) # Part of compilation if not self.no_compilation: mp3_data['TCMP'] = id3.TCMP(encoding=0, text=['1']) # Save mp3_data.save() elif contains_at_least(audio_mime_type, ['audio/x-aac']): # TODO: Add AAC support pass # image_data = __get_tag_image(image_file, audio_mime_type) # aac_data = aac.AAC(audio_file) # if not self.no_track_number: # if track_number > 0 and track_total > 0: # aac_data.add_tags(id3.TRCK(encoding=3, text=['{}/{}'.format(track_number, track_total)])) # # mp3_data['TRCK'] = id3.TRCK(encoding=3, text=[str(track_number)]) # if image_data: # mp3_data['APIC'] = image_data # aac_data.save() elif contains_at_least(audio_mime_type, ['audio/x-flac']): # https://github.com/quodlibet/mutagen/blob/a1db79ece62c4e86259f15825e360d1ce0986a22/mutagen/flac.py # https://github.com/quodlibet/mutagen/blob/4a5d7d17f1a611280cc52d229aa70b77ca3c55dd/tests/test_flac.py flac_data = flac.FLAC(audio_file) # Artwork if not self.no_artwork: image_data = self.__get_tag_image(image_file, audio_mime_type) if image_data: flac_data.add_picture(image_data) # Save flac_data.save() flac_data = File(audio_file) # Track number if not self.no_track_number and track_number > 0: flac_data.tags['tracknumber'] = str(track_number) # Album title if not self.no_album_title and album_title is not None: flac_data.tags['album'] = album_title # Album artist if not self.no_album_artist and album_artist is not None: flac_data.tags['albumartist'] = album_artist # Composer if not self.no_composer and album_composer is not None: flac_data.tags['composer'] = album_composer # Part of compilation if not self.no_compilation: pass # Save flac_data.save() # audio = File(audio_file, easy=True) else: raise InvalidMimeTypeException("Invalid audio format.", audio_mime_type) # Remove artwork if succeeded if os.path.exists(image_file): os.remove(image_file) # Rename filename from id to title dest_audio_file = os.path.join(download_dir, '{}.{}'.format(song_title, self.audio_codec)) os.rename(audio_file, dest_audio_file) dest_audio_filename = os.path.basename(dest_audio_file) logger.info('{}[File:{}] Updated.'.format(log_prefix, dest_audio_filename)) except FileNotFoundError: message = 'File not found. Skipped.' logger.warning('{}[File:{}] {}'.format(log_prefix, audio_filename, message)) except InvalidDataException as e: message = e.message + ' Skipped.' logger.warning('{}[File:{}] {}'.format(log_prefix, audio_filename, message)) except InvalidMimeTypeException as e: message = e.message + ' Skipped.' logger.warning('{}[File:{}] {}'.format(log_prefix, audio_filename, message)) except Exception as e: message = 'Error {}: {} Skipped.'.format(type(e), str(e)) logger.error('{}[File:{}] {}'.format(log_prefix, audio_filename, message))
def dump(input_path, output_path=None, skip=True): output_path = (lambda path, meta: os.path.splitext(path)[0] + '.' + meta[ 'format']) if not output_path else output_path output_path = ( lambda path, meta: path) if not callable(output_path) else output_path core_key = binascii.a2b_hex('687A4852416D736F356B496E62617857') meta_key = binascii.a2b_hex('2331346C6A6B5F215C5D2630553C2728') unpad = lambda s: s[0:-(s[-1] if type(s[-1]) == int else ord(s[-1]))] f = open(input_path, 'rb') # magic header header = f.read(8) assert binascii.b2a_hex(header) == b'4354454e4644414d' # key data f.seek(2, 1) key_length = f.read(4) key_length = struct.unpack('<I', bytes(key_length))[0] key_data = bytearray(f.read(key_length)) key_data = bytes(bytearray([byte ^ 0x64 for byte in key_data])) cryptor = AES.new(core_key, AES.MODE_ECB) key_data = unpad(cryptor.decrypt(key_data))[17:] key_length = len(key_data) # S-box (standard RC4 Key-scheduling algorithm) key = bytearray(key_data) S = bytearray(range(256)) j = 0 for i in range(256): j = (j + S[i] + key[i % key_length]) & 0xFF S[i], S[j] = S[j], S[i] # meta data meta_length = f.read(4) meta_length = struct.unpack('<I', bytes(meta_length))[0] if meta_length: meta_data = bytearray(f.read(meta_length)) meta_data = bytes(bytearray([byte ^ 0x63 for byte in meta_data])) identification = meta_data.decode('utf-8') meta_data = base64.b64decode(meta_data[22:]) cryptor = AES.new(meta_key, AES.MODE_ECB) meta_data = unpad(cryptor.decrypt(meta_data)).decode('utf-8')[6:] meta_data = json.loads(meta_data) else: meta_data = { 'format': 'flac' if os.fstat(f.fileno()).st_size > 1024**2 * 16 else 'mp3' } # crc32 crc32 = f.read(4) crc32 = struct.unpack('<I', bytes(crc32))[0] # album cover f.seek(5, 1) image_size = f.read(4) image_size = struct.unpack('<I', bytes(image_size))[0] image_data = f.read(image_size) if image_size else None # media data output_path = output_path(input_path, meta_data) if skip and os.path.exists(output_path): return data = f.read() f.close() # stream cipher (modified RC4 Pseudo-random generation algorithm) stream = [S[(S[i] + S[(i + S[i]) & 0xFF]) & 0xFF] for i in range(256)] stream = bytes(bytearray(stream * (len(data) // 256 + 1))[1:1 + len(data)]) data = XOR(data, stream) m = open(output_path, 'wb') m.write(data) m.close() # media tag def embed(item, content, type): item.encoding = 0 item.type = type item.mime = 'image/png' if content[0:4] == binascii.a2b_hex( '89504E47') else 'image/jpeg' item.data = content if image_data: if meta_data['format'] == 'flac': audio = flac.FLAC(output_path) image = flac.Picture() embed(image, image_data, 3) audio.clear_pictures() audio.add_picture(image) elif meta_data['format'] == 'mp3': audio = mp3.MP3(output_path) image = id3.APIC() embed(image, image_data, 6) audio.tags.add(image) audio.save() if meta_length: if meta_data['format'] == 'flac': audio = flac.FLAC(output_path) audio['description'] = identification else: audio = mp3.EasyMP3(output_path) audio.tags.RegisterTextKey('comment', 'COMM') audio['comment'] = identification audio['title'] = meta_data['musicName'] audio['album'] = meta_data['album'] audio['artist'] = '/'.join( [artist[0] for artist in meta_data['artist']]) audio.save() return output_path
def set_metadata_tags(args, audio_file, idx, track, ripper): # log completed file print(Fore.GREEN + Style.BRIGHT + os.path.basename(audio_file) + Style.NORMAL + "\t[ " + format_size(os.stat(audio_file)[ST_SIZE]) + " ]" + Fore.RESET) if args.output_type == "wav" or args.output_type == "pcm": print(Fore.YELLOW + "Skipping metadata tagging for " + args.output_type + " encoding...") return # ensure everything is loaded still if not track.is_loaded: track.load() if not track.album.is_loaded: track.album.load() album_browser = track.album.browse() album_browser.load() # calculate num of tracks on disc and num of dics num_discs = 0 num_tracks = 0 for track_browse in album_browser.tracks: if (track_browse.disc == track.disc and track_browse.index > track.index): num_tracks = track_browse.index if track_browse.disc > num_discs: num_discs = track_browse.disc # try to get genres from Spotify's Web API genres = None if args.genres is not None: genres = ripper.web.get_genres(args.genres[0], track) # use mutagen to update id3v2 tags and vorbis comments try: audio = None on_error = 'replace' if args.ascii_path_only else 'ignore' album = to_ascii(track.album.name, on_error) artist = to_ascii(track.artists[0].name, on_error) title = to_ascii(track.name, on_error) # the comment tag can be formatted if args.comment is not None: comment = \ format_track_string(ripper, args.comment[0], idx, track) comment_ascii = to_ascii(comment, on_error) if args.grouping is not None: grouping = \ format_track_string(ripper, args.grouping[0], idx, track) grouping_ascii = to_ascii(grouping, on_error) if genres is not None and genres: genres_ascii = [to_ascii(genre) for genre in genres] # cover art image def get_cover_image(image_link): image_link = 'http://i.scdn.co%s' % ( image_link[len('spotify'):].replace(':', '/')) cover_file = urllib.request.urlretrieve(image_link)[0] with open(cover_file, "rb") as f: if f.mode == "rb": return f.read() else: return None image_link = str(track.album.cover(0).link) image = get_cover_image(image_link) def tag_to_ascii(_str, _str_ascii): return _str if args.ascii_path_only else _str_ascii def idx_of_total_str(_idx, _total): if _total > 0: return "%d/%d" % (_idx, _total) else: return "%d" % (_idx) def save_cover_image(embed_image_func): if image is not None: def write_image(file_name): cover_path = os.path.dirname(audio_file) cover_file = os.path.join(cover_path, file_name) if not path_exists(cover_file): with open(cover_file, "wb") as f: f.write(image) if args.cover_file is not None: write_image(args.cover_file[0]) elif args.cover_file_and_embed is not None: write_image(args.cover_file_and_embed[0]) embed_image_func() else: embed_image_func() def set_id3_tags(audio): # add ID3 tag if it doesn't exist audio.add_tags() def embed_image(): audio.tags.add( id3.APIC( encoding=3, mime='image/jpeg', type=3, desc='Front Cover', data=image ) ) save_cover_image(embed_image) if album is not None: audio.tags.add( id3.TALB(text=[tag_to_ascii(track.album.name, album)], encoding=3)) audio.tags.add( id3.TIT2(text=[tag_to_ascii(track.name, title)], encoding=3)) audio.tags.add( id3.TPE1(text=[tag_to_ascii(track.artists[0].name, artist)], encoding=3)) audio.tags.add(id3.TDRC(text=[str(track.album.year)], encoding=3)) audio.tags.add( id3.TPOS(text=[idx_of_total_str(track.disc, num_discs)], encoding=3)) audio.tags.add( id3.TRCK(text=[idx_of_total_str(track.index, num_tracks)], encoding=3)) if args.comment is not None: audio.tags.add( id3.COMM(text=[tag_to_ascii(comment, comment_ascii)], encoding=3)) if args.grouping is not None: audio.tags.add( id3.TIT1(text=[tag_to_ascii(grouping, grouping_ascii)], encoding=3)) if genres is not None and genres: tcon_tag = id3.TCON(encoding=3) tcon_tag.genres = genres if args.ascii_path_only \ else genres_ascii audio.tags.add(tcon_tag) if args.id3_v23: audio.tags.update_to_v23() audio.save(v2_version=3, v23_sep='/') audio.tags.version = (2, 3, 0) else: audio.save() # aac is not well supported def set_id3_tags_raw(audio, audio_file): try: id3_dict = id3.ID3(audio_file) except id3.ID3NoHeaderError: id3_dict = id3.ID3() def embed_image(): id3_dict.add( id3.APIC( encoding=3, mime='image/jpeg', type=3, desc='Front Cover', data=image ) ) save_cover_image(embed_image) if album is not None: id3_dict.add( id3.TALB(text=[tag_to_ascii(track.album.name, album)], encoding=3)) id3_dict.add( id3.TIT2(text=[tag_to_ascii(track.name, title)], encoding=3)) id3_dict.add( id3.TPE1(text=[tag_to_ascii(track.artists[0].name, artist)], encoding=3)) id3_dict.add(id3.TDRC(text=[str(track.album.year)], encoding=3)) id3_dict.add( id3.TPOS(text=[idx_of_total_str(track.disc, num_discs)], encoding=3)) id3_dict.add( id3.TRCK(text=[idx_of_total_str(track.index, num_tracks)], encoding=3)) if args.comment is not None: id3_dict.add( id3.COMM(text=[tag_to_ascii(comment, comment_ascii)], encoding=3)) if args.grouping is not None: audio.tags.add( id3.TIT1(text=[tag_to_ascii(grouping, grouping_ascii)], encoding=3)) if genres is not None and genres: tcon_tag = id3.TCON(encoding=3) tcon_tag.genres = genres if args.ascii_path_only \ else genres_ascii id3_dict.add(tcon_tag) if args.id3_v23: id3_dict.update_to_v23() id3_dict.save(audio_file, v2_version=3, v23_sep='/') id3_dict.version = (2, 3, 0) else: id3_dict.save(audio_file) audio.tags = id3_dict def set_vorbis_comments(audio): # add Vorbis comment block if it doesn't exist if audio.tags is None: audio.add_tags() def embed_image(): pic = flac.Picture() pic.type = 3 pic.mime = "image/jpeg" pic.desc = "Front Cover" pic.data = image if args.output_type == "flac": audio.add_picture(pic) else: data = base64.b64encode(pic.write()) audio["METADATA_BLOCK_PICTURE"] = [data.decode("ascii")] save_cover_image(embed_image) if album is not None: audio.tags["ALBUM"] = tag_to_ascii(track.album.name, album) audio.tags["TITLE"] = tag_to_ascii(track.name, title) audio.tags["ARTIST"] = tag_to_ascii(track.artists[0].name, artist) audio.tags["DATE"] = str(track.album.year) audio.tags["YEAR"] = str(track.album.year) audio.tags["DISCNUMBER"] = str(track.disc) audio.tags["DISCTOTAL"] = str(num_discs) audio.tags["TRACKNUMBER"] = str(track.index) audio.tags["TRACKTOTAL"] = str(num_tracks) if args.comment is not None: audio.tags["COMMENT"] = tag_to_ascii(comment, comment_ascii) if args.grouping is not None: audio.tags["GROUPING"] = \ tag_to_ascii(grouping, grouping_ascii) if genres is not None and genres: _genres = genres if args.ascii_path_only else genres_ascii audio.tags["GENRE"] = ", ".join(_genres) audio.save() # only called by Python 3 def set_mp4_tags(audio): # add MP4 tags if it doesn't exist if audio.tags is None: audio.add_tags() def embed_image(): audio.tags["covr"] = mp4.MP4Cover(image) save_cover_image(embed_image) if album is not None: audio.tags["\xa9alb"] = tag_to_ascii(track.album.name, album) audio["\xa9nam"] = tag_to_ascii(track.name, title) audio.tags["\xa9ART"] = tag_to_ascii(track.artists[0].name, artist) audio.tags["\xa9day"] = str(track.album.year) audio.tags["disk"] = [(track.disc, num_discs)] audio.tags["trkn"] = [(track.index, num_tracks)] if args.comment is not None: audio.tags["\xa9cmt"] = tag_to_ascii(comment, comment_ascii) if args.grouping is not None: audio.tags["\xa9grp"] = tag_to_ascii(grouping, grouping_ascii) if genres is not None and genres: _genres = genres if args.ascii_path_only else genres_ascii audio.tags["\xa9gen"] = ", ".join(_genres) audio.save() def set_m4a_tags(audio): # add M4A tags if it doesn't exist audio.add_tags() def embed_image(): audio.tags[str("covr")] = m4a.M4ACover(image) save_cover_image(embed_image) if album is not None: audio.tags[b"\xa9alb"] = tag_to_ascii(track.album.name, album) audio[b"\xa9nam"] = tag_to_ascii(track.name, title) audio.tags[b"\xa9ART"] = tag_to_ascii( track.artists[0].name, artist) audio.tags[b"\xa9day"] = str(track.album.year) audio.tags[str("disk")] = (track.disc, num_discs) audio.tags[str("trkn")] = (track.index, num_tracks) if args.comment is not None: audio.tags[b"\xa9cmt"] = tag_to_ascii(comment, comment_ascii) if args.grouping is not None: audio.tags[b"\xa9grp"] = tag_to_ascii(grouping, grouping_ascii) if genres is not None and genres: _genres = genres if args.ascii_path_only else genres_ascii audio.tags[b"\xa9gen"] = ", ".join(_genres) audio.save() if args.output_type == "flac": audio = flac.FLAC(audio_file) set_vorbis_comments(audio) elif args.output_type == "ogg": audio = oggvorbis.OggVorbis(audio_file) set_vorbis_comments(audio) elif args.output_type == "opus": audio = oggopus.OggOpus(audio_file) set_vorbis_comments(audio) elif args.output_type == "aac": audio = aac.AAC(audio_file) set_id3_tags_raw(audio, audio_file) elif args.output_type == "m4a": if sys.version_info >= (3, 0): from mutagen import mp4 audio = mp4.MP4(audio_file) set_mp4_tags(audio) else: from mutagen import m4a, mp4 audio = m4a.M4A(audio_file) set_m4a_tags(audio) audio = mp4.MP4(audio_file) elif args.output_type == "alac.m4a": if sys.version_info >= (3, 0): from mutagen import mp4 audio = mp4.MP4(audio_file) set_mp4_tags(audio) else: from mutagen import m4a, mp4 audio = m4a.M4A(audio_file) set_m4a_tags(audio) audio = mp4.MP4(audio_file) elif args.output_type == "mp3": audio = mp3.MP3(audio_file, ID3=id3.ID3) set_id3_tags(audio) def bit_rate_str(bit_rate): brs = "%d kb/s" % bit_rate if not args.cbr: brs = "~" + brs return brs def mode_str(mode): modes = ["Stereo", "Joint Stereo", "Dual Channel", "Mono"] if mode < len(modes): return modes[mode] else: return "" def channel_str(num): channels = ["", "Mono", "Stereo"] if num < len(channels): return channels[num] else: return "" # log id3 tags print("-" * 79) print(Fore.YELLOW + "Setting artist: " + artist + Fore.RESET) if album is not None: print(Fore.YELLOW + "Setting album: " + album + Fore.RESET) print(Fore.YELLOW + "Setting title: " + title + Fore.RESET) print(Fore.YELLOW + "Setting track info: (" + str(track.index) + ", " + str(num_tracks) + ")" + Fore.RESET) print(Fore.YELLOW + "Setting disc info: (" + str(track.disc) + ", " + str(num_discs) + ")" + Fore.RESET) print(Fore.YELLOW + "Setting release year: " + str(track.album.year) + Fore.RESET) if genres is not None and genres: print(Fore.YELLOW + "Setting genres: " + " / ".join(genres_ascii) + Fore.RESET) if image is not None: print(Fore.YELLOW + "Adding cover image" + Fore.RESET) if args.comment is not None: print(Fore.YELLOW + "Adding comment: " + comment_ascii + Fore.RESET) if args.grouping is not None: print(Fore.YELLOW + "Adding grouping: " + grouping_ascii + Fore.RESET) if args.output_type == "flac": bit_rate = ((audio.info.bits_per_sample * audio.info.sample_rate) * audio.info.channels) print("Time: " + format_time(audio.info.length) + "\tFree Lossless Audio Codec" + "\t[ " + bit_rate_str(bit_rate / 1000) + " @ " + str(audio.info.sample_rate) + " Hz - " + channel_str(audio.info.channels) + " ]") print("-" * 79) print(Fore.YELLOW + "Writing Vorbis comments - " + audio.tags.vendor + Fore.RESET) print("-" * 79) if args.output_type == "alac.m4a": bit_rate = ((audio.info.bits_per_sample * audio.info.sample_rate) * audio.info.channels) print("Time: " + format_time(audio.info.length) + "\tApple Lossless" + "\t[ " + bit_rate_str(bit_rate / 1000) + " @ " + str(audio.info.sample_rate) + " Hz - " + channel_str(audio.info.channels) + " ]") print("-" * 79) print(Fore.YELLOW + "Writing Apple iTunes metadata - " + Fore.RESET) print("-" * 79) elif args.output_type == "ogg": print("Time: " + format_time(audio.info.length) + "\tOgg Vorbis Codec" + "\t[ " + bit_rate_str(audio.info.bitrate / 1000) + " @ " + str(audio.info.sample_rate) + " Hz - " + channel_str(audio.info.channels) + " ]") print("-" * 79) print(Fore.YELLOW + "Writing Vorbis comments - " + audio.tags.vendor + Fore.RESET) print("-" * 79) elif args.output_type == "opus": print("Time: " + format_time(audio.info.length) + "\tOpus Codec" + "\t[ " + channel_str(audio.info.channels) + " ]") print("-" * 79) print(Fore.YELLOW + "Writing Vorbis comments - " + audio.tags.vendor + Fore.RESET) print("-" * 79) elif args.output_type == "mp3": print("Time: " + format_time(audio.info.length) + "\tMPEG" + str(audio.info.version) + ", Layer " + ("I" * audio.info.layer) + "\t[ " + bit_rate_str(audio.info.bitrate / 1000) + " @ " + str(audio.info.sample_rate) + " Hz - " + mode_str(audio.info.mode) + " ]") print("-" * 79) id3_version = "v%d.%d" % ( audio.tags.version[0], audio.tags.version[1]) print("ID3 " + id3_version + ": " + str(len(audio.tags.values())) + " frames") print( Fore.YELLOW + "Writing ID3 version " + id3_version + Fore.RESET) print("-" * 79) elif args.output_type == "aac": print("Time: " + format_time(audio.info.length) + "\tAdvanced Audio Coding" + "\t[ " + bit_rate_str(audio.info.bitrate / 1000) + " @ " + str(audio.info.sample_rate) + " Hz - " + channel_str(audio.info.channels) + " ]") print("-" * 79) id3_version = "v%d.%d" % ( audio.tags.version[0], audio.tags.version[1]) print("ID3 " + id3_version + ": " + str(len(audio.tags.values())) + " frames") print( Fore.YELLOW + "Writing ID3 version " + id3_version + Fore.RESET) print("-" * 79) elif args.output_type == "m4a": bit_rate = ((audio.info.bits_per_sample * audio.info.sample_rate) * audio.info.channels) print("Time: " + format_time(audio.info.length) + "\tMPEG-4 Part 14 Audio" + "\t[ " + bit_rate_str(bit_rate / 1000) + " @ " + str(audio.info.sample_rate) + " Hz - " + channel_str(audio.info.channels) + " ]") print("-" * 79) print(Fore.YELLOW + "Writing Apple iTunes metadata - " + str(audio.info.codec) + Fore.RESET) print("-" * 79) except id3.error: print(Fore.YELLOW + "Warning: exception while saving id3 tag: " + str(id3.error) + Fore.RESET)
def dump(input_path, output_path=None, skip=True): output_path = (lambda path, meta: os.path.splitext(path)[0] + '.' + meta[ 'format']) if not output_path else output_path core_key = binascii.a2b_hex('687A4852416D736F356B496E62617857') meta_key = binascii.a2b_hex('2331346C6A6B5F215C5D2630553C2728') unpad = lambda s: s[0:-(s[-1] if type(s[-1]) == int else ord(s[-1]))] f = open(input_path, 'rb') # magic header header = f.read(8) assert binascii.b2a_hex(header) == b'4354454e4644414d' # key data f.seek(2, 1) key_length = f.read(4) key_length = struct.unpack('<I', bytes(key_length))[0] key_data = bytearray(f.read(key_length)) key_data = bytes(bytearray([byte ^ 0x64 for byte in key_data])) cryptor = AES.new(core_key, AES.MODE_ECB) key_data = unpad(cryptor.decrypt(key_data))[17:] key_length = len(key_data) # S-box (standard RC4 Key-scheduling algorithm) key = bytearray(key_data) S = bytearray(range(256)) j = 0 for i in range(256): j = (j + S[i] + key[i % key_length]) % 256 S[i], S[j] = S[j], S[i] # meta data meta_length = f.read(4) meta_length = struct.unpack('<I', bytes(meta_length))[0] meta_data = bytearray(f.read(meta_length)) meta_data = bytes(bytearray([byte ^ 0x63 for byte in meta_data])) meta_data = base64.b64decode(meta_data[22:]) cryptor = AES.new(meta_key, AES.MODE_ECB) meta_data = unpad(cryptor.decrypt(meta_data)).decode('utf-8')[6:] meta_data = json.loads(meta_data) # crc32 crc32 = f.read(4) crc32 = struct.unpack('<I', bytes(crc32))[0] # album cover f.seek(5, 1) image_size = f.read(4) image_size = struct.unpack('<I', bytes(image_size))[0] image_data = f.read(image_size) # media data output_path = output_path(input_path, meta_data) if skip and os.path.exists(output_path): return m = open(output_path, 'wb') data = bytearray(f.read()) # stream cipher (modified RC4 Pseudo-random generation algorithm) i = 0 j = 0 for k, _ in enumerate(data): i = (i + 1) % 256 j = (i + S[i]) % 256 # in RC4, is j = (j + S[i]) % 256 # S[i], S[j] = S[j], S[i] # no swapping data[k] ^= S[(S[i] + S[j]) % 256] m.write(data) m.close() f.close() # media tag if meta_data['format'] == 'flac': audio = flac.FLAC(output_path) # audio.delete() image = flac.Picture() image.encoding = 0 image.type = 3 image.mime = 'image/jpeg' image.data = image_data audio.clear_pictures() audio.add_picture(image) elif meta_data['format'] == 'mp3': audio = mp3.MP3(output_path) # audio.delete() image = id3.APIC() image.encoding = 0 image.type = 3 image.mime = 'image/jpeg' image.data = image_data audio.tags.add(image) audio.save() audio = mp3.EasyMP3(output_path) audio['title'] = meta_data['musicName'] audio['album'] = meta_data['album'] audio['artist'] = '/'.join([artist[0] for artist in meta_data['artist']]) audio.save() return output_path
def test_identify_flac_filetype(flac_file): """Test the identify_filetype function on a FLAC file. Asserts that the identify_filetype function returns a FLAC object created by mutagen.""" assert metadata.identify_filetype(flac_file) == flac.FLAC(flac_file)
def dumpfile(file_path): # 预定义key core_key = binascii.a2b_hex("687A4852416D736F356B496E62617857") meta_key = binascii.a2b_hex("2331346C6A6B5F215C5D2630553C2728") unpad = lambda s: s[0:-(s[-1] if type(s[-1]) == int else ord(s[-1]))] f = open(file_path, 'rb') # magic header header = f.read(8) assert binascii.b2a_hex(header) == b'4354454e4644414d' # key data f.seek(2, 1) key_length = f.read(4) key_length = struct.unpack('<I', bytes(key_length))[0] key_data = bytearray(f.read(key_length)) key_data = bytes(bytearray([byte ^ 0x64 for byte in key_data])) cryptor = AES.new(core_key, AES.MODE_ECB) key_data = unpad(cryptor.decrypt(key_data))[17:] key_length = len(key_data) # key box key_data = bytearray(key_data) key_box = bytearray(range(256)) j = 0 for i in range(256): j = (key_box[i] + j + key_data[i % key_length]) & 0xff key_box[i], key_box[j] = key_box[j], key_box[i] # meta data meta_length = f.read(4) meta_length = struct.unpack('<I', bytes(meta_length))[0] meta_data = bytearray(f.read(meta_length)) meta_data = bytes(bytearray([byte ^ 0x63 for byte in meta_data])) meta_data = base64.b64decode(meta_data[22:]) cryptor = AES.new(meta_key, AES.MODE_ECB) meta_data = unpad(cryptor.decrypt(meta_data)).decode('utf-8')[6:] meta_data = json.loads(meta_data) # crc32 crc32 = f.read(4) crc32 = struct.unpack('<I', bytes(crc32))[0] # album cover f.seek(5, 1) image_size = f.read(4) image_size = struct.unpack('<I', bytes(image_size))[0] image_data = f.read(image_size) author_name = "" # media data if len(meta_data['artist']) == 1: author_name = meta_data['artist'][0][0] else: for i in range(len(meta_data['artist'])): if i == len(meta_data['artist']) - 1: author_name += meta_data['artist'][i][0] else: author_name += meta_data['artist'][i][0] author_name += ',' file_name = author_name + ' - ' + meta_data['musicName'] + '.' + meta_data['format'] music_path = os.path.join(os.path.split(file_path)[0], file_name) m = open(music_path, 'wb') while True: chunk = bytearray(f.read(0x8000)) chunk_length = len(chunk) if not chunk: break for i in range(chunk_length): j = (i + 1) & 0xff chunk[i] ^= key_box[(key_box[j] + key_box[(key_box[j] + j) & 0xff]) & 0xff] m.write(chunk) m.close() f.close() # media tag if meta_data['format'] == 'flac': audio = flac.FLAC(music_path) # audio.delete() image = flac.Picture() image.type = 3 image.mime = 'image/jpeg' image.data = image_data audio.clear_pictures() audio.add_picture(image) elif meta_data['format'] == 'mp3': audio = mp3.MP3(music_path) # audio.delete() image = id3.APIC() image.type = 3 image.mime = 'image/jpeg' image.data = image_data audio.tags.add(image) audio.save() audio = mp3.EasyMP3(music_path) audio['title'] = meta_data['musicName'] audio['album'] = meta_data['album'] audio['artist'] = '/'.join([artist[0] for artist in meta_data['artist']]) audio.save()
import os import sys import mutagen.flac as mflac import mutagen.id3 as mid3 input_song = sys.argv[1] proc_song = () title, extension = os.path.splitext(input_song) true_extension = extension.lower() if true_extension == '.mp3': proc_song = mid3.ID3(input_song) elif true_extension == '.flac': proc_song = mflac.FLAC(input_song) else: print("Extension not supported") sys.exit(1) output = proc_song.pprint() print(output)
def dump(input_path): f = open(input_path, 'rb') # 开头都一样,不一样解不了密 header = f.read(8) assert binascii.b2a_hex(header) == b'4354454e4644414d' f.seek(2, 1) # key data, 要翻译成小端的unsigned int key_length = f.read(4) key_length = struct.unpack('<I', bytes(key_length))[0] # AES-ECB 密文 key_data = bytearray(f.read(key_length)) key_data = bytes(bytearray([byte ^ 0x64 for byte in key_data])) # AES-ECB 解密 core_key = binascii.a2b_hex('687A4852416D736F356B496E62617857') # 神奇的key,hashcat还是有内鬼? cryptor = AES.new(core_key, AES.MODE_ECB) # decrypt 前17位是 neteasecloudmusic,采用pkcs7 padding方式 key_data = unpad(cryptor.decrypt(key_data), 16)[17:] key_length = len(key_data) # S-box (标准 RC4 KSA) key = bytearray(key_data) S = bytearray(range(256)) j = 0 for i in range(256): j = (j + S[i] + key[i % key_length]) & 0xFF S[i], S[j] = S[j], S[i] # meta data,要翻译成小端的unsigned int meta_length = f.read(4) meta_length = struct.unpack('<I', bytes(meta_length))[0] if meta_length: meta_data = bytearray(f.read(meta_length)) meta_data = bytes(bytearray([byte ^ 0x63 for byte in meta_data])) identifier = meta_data.decode('utf-8') # 惊现 '163 key(Don't modify):' 内鬼? # base64 解码 meta_data = base64.b64decode(meta_data[22:]) # 第二次 AES-ECB 解密 meta_key = binascii.a2b_hex('2331346C6A6B5F215C5D2630553C2728') cryptor = AES.new(meta_key, AES.MODE_ECB) meta_data = unpad(cryptor.decrypt(meta_data), 16).decode('utf-8') # 解密出来一个 json 格式文件 meta_data = json.loads(meta_data[6:]) else: # 没有 json 文件确定格式的话,就用文件大小区分(>16m) meta_data = {'format': 'flac' if os.fstat(f.fileno()).st_size > 16777216 else 'mp3'} f.seek(5, 1) # 专辑封面图片 image_space = f.read(4) image_space = struct.unpack('<I', bytes(image_space))[0] image_size = f.read(4) image_size = struct.unpack('<I', bytes(image_size))[0] image_data = f.read(image_size) if image_size else None f.seek(image_space - image_size, 1) # 音乐输出地址 output_path = os.path.splitext(input_path)[0] + '.' + meta_data['format'] # 已转换过的不做无用功 if os.path.exists(output_path): return # 剩下全是音乐文件 data = f.read() f.close() # 音乐文件主体部分 (修改的RC4-PRGA,没有用 j 加和随机化) # 直接循环的话会丢失最后几秒 stream = [S[(S[i] + S[(i + S[i]) & 0xFF]) & 0xFF] for i in range(256)] stream = bytes(bytearray(stream * (len(data) // 256 + 1))[1:1 + len(data)]) data = strxor(data, stream) m = open(output_path, 'wb') m.write(data) m.close() # 处理专辑封面 if image_data: if meta_data['format'] == 'flac': audio = flac.FLAC(output_path) image = flac.Picture() image.encoding = 0 image.type = 3 image.mime = 'image/png' if image_data[0:4] == binascii.a2b_hex('89504E47') else 'image/jpeg' # png开头是\x89 P N G image.data = image_data audio.clear_pictures() audio.add_picture(image) audio.save() elif meta_data['format'] == 'mp3': audio = mp3.MP3(output_path) image = id3.APIC() image.encoding = 0 image.type = 6 image.mime = 'image/png' if image_data[0:4] == binascii.a2b_hex('89504E47') else 'image/jpeg' # png开头是\x89 P N G image.data = image_data audio.tags.add(image) audio.save() # 添加音乐相关信息 if meta_length: if meta_data['format'] == 'flac': audio = flac.FLAC(output_path) audio['description'] = identifier else: audio = mp3.EasyMP3(output_path) audio['title'] = 'placeholder' audio.tags.RegisterTextKey('comment', 'COMM') audio['comment'] = identifier audio['title'] = meta_data['musicName'] audio['album'] = meta_data['album'] audio['artist'] = '/'.join([artist[0] for artist in meta_data['artist']]) audio.save() return output_path
def get_tags(folder,addformat=False): flacs = [] mp3s = [] images = [] tracks = [] for root, dirnames, filenames in os.walk(folder): for filename in [f for f in filenames if f.endswith(('.flac'))]: flacs.append(os.path.join(root, filename)) for filename in [f for f in filenames if f.endswith('.mp3')]: mp3s.append(os.path.join(root, filename)) for filename in [f for f in filenames if f.endswith('cover.jpg')]: images.append(os.path.join(root, filename)) tracks = [] tracks_tags = [] total_length = 0 if flacs: format = 'FLAC' flacfile = flac.FLAC(flacs[0]) if flacfile.info.bits_per_sample == 16: bitrate = 'Lossless' elif flacfile.info.bits_per_sample == 24: bitrate = '24bit Lossless' ## past here is irrelevant if addformat == True artist = flacfile['artist'][0] album = flacfile['album'][0] year = flacfile['date'][0] for filename in flacs: flacfile = flac.FLAC(filename) track_num = int(flacfile['tracknumber'][0]) track_title = flacfile['title'][0] length = int(flacfile.info.length) tracks.append((track_num, track_title, length)) total_length += length tracks_tags.append(flacfile.tags) elif mp3s: format = 'MP3' mp3file = mp3.MP3(mp3s[0]) if mp3file.info.VBR_preset: ## only for LAME V* bitrate = mp3file.info.VBR_preset else: bitrate = mp3file.info.bitrate / 1000 ## past here is irrelevant if addformat == True artist = str(mp3file['TPE1']) album = str(mp3file['TALB']) year = str(mp3file['TDRC']) for filename in mp3s: mp3file = mp3.MP3(filename) track_num = int(mp3file['TRCK'].text[0]) track_title = str(mp3file['TIT2'].text[0]) length = int(mp3file.info.length) tracks.append((track_num, track_title, length)) total_length += length else: raise Exception('No music files found!') tracks.sort() if images and not addformat: image = to_imgur(images[0]) else: image = '' pprint('Tags extracted from files...') return (artist,album,year,format,bitrate,image,tracks,total_length)
def convert_perfect_three(folder=None,outfolder=None): #folders are created within outfolder for each bitrate #e.g., if outfolder = /files/, three folders would be created #/files/whatever [V2] , /files/whatever [V0] , /files/whatever [320] if not folder: folder = select_folder(msg='Select the folder to be converted') if not outfolder: outfolder = select_folder(msg='Select the folder where the new\ folders should be placed') #get files flacs = [] #contains tuples: (abspath, rel_path, filename) logs_cues = [] #same. we don't copy these etc = [] #same. we do copy these for root, dirs, filenames in os.walk(folder): for filename in filenames: abspath = os.path.join(root,filename) if filename.endswith('.flac'): flacs.append((abspath,relpath(abspath,folder),filename)) elif filename.endswith('.log') or filename.endswith('.cue'): logs_cues.append((abspath,relpath(abspath,folder),filename)) else: etc.append((abspath,relpath(abspath,folder),filename)) #get metadata files = [] ## tuples: ((abspath,rel_path,filename),metadata) if not flacs: raise Exception('No flacs found for conversion!') for f in flacs: flacfile = flac.FLAC(f[0]) artist = flacfile['artist'][0].encode('utf-8') title = flacfile['title'][0].encode('utf-8') album = flacfile['album'][0].encode('utf-8') year = flacfile['date'][0].encode('utf-8') track_num = flacfile['tracknumber'][0].encode('utf-8') metadata = (artist,title,album,year,track_num) files.append((f,metadata)) for f in flacs: reldir = os.path.split(f[1])[0] ## print(reldir) #for debugging if reldir and not os.path.isdir(reldir): os.makedirs(reldir) #prepare the different convert bathes converts = [] return_dirs = [] global _bitrates try: bitrates = _bitrates except NameError: bitrates = ('V0','V2','320') for bitrate in bitrates: #using values of 'artist' and 'album' left over from the above for loop makedir = os.path.normpath( os.path.join(outfolder,'%s - %s (%s) [%s]' % ( artist,album,year,bitrate))) return_dirs.append(makedir) converts.append([(f[0][0], os.path.join( str(makedir),str(f[0][1][:-5])), #-5 = '.flac' make_lame_params(bitrate), make_lame_metadata(f[1])) for f in files]) try: os.mkdir(makedir) except OSError: pass #folder already exists - that's fine #copy over 'etc' (cover art, anything else) for f in etc: reldir = os.path.join(makedir,os.path.split(f[1])[0]) if not os.path.isdir(reldir): os.makedirs(reldir) filecopy(f[0],os.path.join(makedir,f[1])) #make subdirectories, if any exist for convert in converts: for job in convert: reldir = os.path.split(job[1])[0] if not os.path.isdir(reldir): os.makedirs(reldir) ## return converts ##for debugging convert_id = 0 for convert in converts: pprint('converting to %s' % return_dirs[convert_id]) convert_id += 1 jobid = 0 for job in convert: jobid += 1 while actives() == threads_number: time.sleep(1) pprint('%s of %s' % (jobid,len(convert))) threading.Thread( target = flac_to_mp3, args = (job[0],job[1],job[2],job[3])).start() #wait for all threads to finish while actives() != default_actives: time.sleep(1) return return_dirs