def test_mp4_movie_tagging(): movie = tmdb.record_from_query('Star Wars Episode II') filename = pkg_resources.resource_filename( __name__, os.path.join('data', 'movie.mp4')) with tempfile.NamedTemporaryFile() as tmp: with open(filename, 'rb') as original: tmp.write(original.read()) tmp.flush() tmp.seek(0) tmdb.retag(tmp.name, movie) rewritten = mp4.MP4(tmp) nose.tools.eq_(rewritten.tags['\xa9nam'][0], movie.title) nose.tools.eq_(rewritten.tags['desc'][0], movie.tagline) nose.tools.eq_(rewritten.tags['ldes'][0], movie.overview) nose.tools.eq_(rewritten.tags['\xa9day'][0], movie.release_date) nose.tools.eq_(rewritten.tags['stik'], [9]) #nose.tools.eq_(rewritten.tags['hdvd"] = self.HD nose.tools.eq_(sorted(rewritten.tags['\xa9gen']), sorted([k['name'] for k in movie.genres])) us_cert = rewritten.tags["----:com.apple.iTunes:iTunEXTC"][0].decode() nose.tools.eq_(us_cert, 'mpaa|PG|200|') #ensures we can parse the embedded XML document plist = rewritten.tags['----:com.apple.iTunes:iTunMOVI'][0] xml = ElementTree.fromstring(plist) nose.tools.eq_(xml.tag, 'plist') #check cover is present covr = rewritten['covr'] nose.tools.eq_(len(covr), 1) covr = covr[0] assert len(covr) != 0
def mp4Reader( filePath ): """ Read metadata from MP4 file and parse into 'internal' format Arguments: filePath (str): Path to MP4 file to extract metadata from Keyword arguments: None Returns: dict: Dictionary containing metadata if found, otherwise, emtpy dict """ log = logging.getLogger(__name__) obj = mp4.MP4( filePath ) # Create mutagen.mp4.MP4 object info = {} # Initialize empty dictionary for mp4Key, val in obj.items(): # Iterate over key/value pairs in MP4 if isinstance(val, (tuple,list)): # If value is an iterable val = [v.decode() if isinstance(v, mp4.MP4FreeForm) else v for v in val] # Decode every MP4FreeForm type element in the iterable if len(val) == 1: # If only one (1) element in iterable val = val[0] # Take just that value key = MP42COMMON.get(mp4Key, None) # Get common key if exists if key: # If common key found info[key] = val # Update info dictionary else: log.debug('Invalid key : {}'.format(mp4Key)) # Debug info info = _mp4ExtractCover( info ) return info
def _downloadAudio(self, video): filepath = os.environ.get( 'HOME') + '/Downloads/YouTubePlayer/' + video.title + '[audio].m4a' audio = video.m4astreams[-1] try: audio.download( filepath, quiet=False, callback=self._setdownloadETA) except FileNotFoundError: os.makedirs(os.environ.get('HOME') + '/Downloads/YouTubePlayer') audio.getbestaudio(preftype="m4a").download( filepath, quiet=False, callback=self._setdownloadETA) metadata = self._getMetadata(video) if metadata is None: return self.infoLabel.set_text('Fixing metadata') audiofile = mp4.MP4(filepath) audiofile['\xa9nam'] = metadata['track_title'] audiofile['\xa9ART'] = metadata['artist'] audiofile['\xa9alb'] = metadata['album'] audiofile['aART'] = metadata['artist'] cover = metadata['album_art_url'] fd = urllib.request.urlopen(cover) covr = mp4.MP4Cover(fd.read(), getattr(mp4.MP4Cover, 'FORMAT_PNG' if cover.endswith('png') else 'FORMAT_JPEG')) fd.close() audiofile['covr'] = [covr] audiofile.save() self.infoLabel.set_text('Metadata fixed')
def processm4a(filename): '''Reads metadata from MPEG-4 audio files''' f = easymp4.EasyMP4(filename) title, anime, role, rolequal = part(f.get('title', _blank)[0]) artist = f.get('artist', _blank)[0] fn_artist = artist if artist else 'Unknown Artist' label = f.get('description', _blank)[0] album = f.get('album', _blank)[0] id = random_id() f = mp4.MP4(filename) composer = f.get('©wrt', _blank)[0] return [ metadata( ID=id, OriginalFileName=filename, Filename=canonicalFileName(id, fn_artist, title, 'm4a'), Tracktitle=title, Album=album, Length=int(f.info.length * 1000), Anime=anime, Role=role, Rolequalifier=rolequal, Artist=artist, Composer=composer, Label=label, InMyriad="NO", Dateadded=datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M")) ]
def get_file(filename): extension = filename[filename.rfind('.'):len(filename)] if extension == '.mp3': return mp3.MP3(filename), 'mp3' if extension == '.mp4' or extension == '.m4a': return mp4.MP4(filename), 'mp4'
def do_one(self, number, fn): bn = os.path.basename(fn) dn = os.path.dirname(fn) result = re.match('(\d*);(.*?);(.*).(m4a)', bn) if not result: print u'bad file name format: {0}'.format(bn) return _, youtube_id, title, ext = result.groups() disk_number = number / self.tracks_per_disk + 1 track_number = number % self.tracks_per_disk + 1 # try to split title to artist - song name result = re.match('(.*) - (.*)', title) if result: artist, song_name = result.groups() else: artist, song_name = '', title f = mp4.MP4(fn) f.tags['trkn'] = [(track_number, self.tracks_per_disk)] f.tags['disk'] = [(disk_number, self.num_disks)] f.tags['cpil'] = True f.tags['\xa9alb'] = [ self.album, ] f.tags['\xa9nam'] = [ song_name, ] f.tags['\xa9ART'] = [ artist, ] f.tags['\xa9gen'] = [ self.genre, ] f.tags['\xa9cmt'] = [ youtube_id, ] jpg_file = os.path.splitext(fn)[0] + '.jpg' with open(jpg_file, 'rb') as h: cover_data = h.read() cover = mp4.MP4Cover(cover_data, mp4.AtomDataType.JPEG) f.tags['covr'] = [ cover, ] f.save() os.unlink(jpg_file) new_fn = os.path.join(dn, title + '.' + ext) try: os.rename(fn, new_fn) except WindowsError as e: print u'cannot rename {0}'.format(number) print u'error {0}: {1}'.format(e.args[0], e.args[1]) self.xl.add_song(number, youtube_id, title, song_name, artist)
def __init__(self, path): super(Track, self).__init__(path) self.m4a = mp4.MP4(path) f = self.m4a self.title = l(f.get(b'\xa9nam')) self.discnumber = reader.number(l(f.get(b'disk'))) self.tracknumber = reader.number(l(f.get(b'trkn'))) self.length = int(f.info.length) self.bitrate = int(f.info.bitrate) self.musicbrainz_trackid = l( f.get(b'----:com.apple.iTunes:MusicBrainz Track Id')) self.genres = f.get(b'\xa9gen') artist = reader.Artist() artist.name = l(f.get(b'\xa9ART')) artist.sortname = l(f.get(b'soar')) artist.musicbrainz_artistid = [ i.decode('utf-8') for i in f.get(b'----:com.apple.iTunes:MusicBrainz Artist Id', []) ] self.artist = artist for a, i in zip( f.get(b'----:com.apple.iTunes:ARTISTS', []), f.get(b'----:com.apple.iTunes:MusicBrainz Artist Id', []), ): artist = reader.Artist() artist.name = a artist.musicbrainz_artistid = i self.artists.append(artist) album = reader.Album() album.title = l(f.get(b'\xa9alb')) album.date = l(f.get(b'\xa9day')) album.country = l( f.get(b'----:com.apple.iTunes:MusicBrainz Album Release Country')) album.musicbrainz_albumid = l( f.get(b'----:com.apple.iTunes:MusicBrainz Album Id')) album.musicbrainz_releasegroupid = l( f.get(b'----:com.apple.iTunes:MusicBrainz Release Group Id')) album.labels = f.get(b'----:com.apple.iTunes:LABEL') album.albumtypes = f.get( b'----:com.apple.iTunes:MusicBrainz Album Type') album.albumstatus = f.get( b'----:com.apple.iTunes:MusicBrainz Album Status') albumartist = reader.Artist() albumartist.name = l(f.get(b'aART')) albumartist.sortname = l(f.get(b'soaa')) albumartist.musicbrainz_artistid = [ i.decode('utf-8') for i in f.get( b'----:com.apple.iTunes:MusicBrainz Album Artist Id', []) ] album.artist = albumartist self.album = album
def tags_mp4(name, infer=True): try: tags = mp4.MP4(name) except (mp4.MP4StreamInfoError, mutagen.mp4.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['\xa9ART'][0] if '\xa9ART' in tags.keys() else None,\ tags['\xa9alb'][0] if '\xa9alb' in tags.keys() else None
def test_mp4_episode_tagging(): episode = tvdb.record_from_query('Friends', 1, 1) #season 1, episode 1 filename = pkg_resources.resource_filename( __name__, os.path.join('data', 'movie.mp4')) with tempfile.NamedTemporaryFile() as tmp: with open(filename, 'rb') as original: tmp.write(original.read()) tmp.flush() tmp.seek(0) tvdb.retag(tmp.name, episode) rewritten = mp4.MP4(tmp) nose.tools.eq_(rewritten["tvsh"][0], episode.season.show.SeriesName) nose.tools.eq_(rewritten["\xa9nam"][0], episode.EpisodeName) nose.tools.eq_(rewritten["tven"][0], episode.EpisodeName) assert rewritten["desc"][0].startswith(episode.Overview) nose.tools.eq_(rewritten["ldes"][0], episode.Overview) nose.tools.eq_(rewritten["tvnn"][0], episode.season.show.Network) nose.tools.eq_(rewritten["\xa9day"][0], episode.FirstAired.strftime('%Y-%m-%d')) nose.tools.eq_(rewritten["tvsn"], [episode.season.season_number]) nose.tools.eq_( rewritten["disk"], [(episode.season.season_number, len(episode.season.show))]) nose.tools.eq_(rewritten["\xa9alb"][0], '%s, Season %d' % \ (episode.season.show.SeriesName, episode.season.season_number)) nose.tools.eq_(rewritten["tves"], [episode.EpisodeNumber]) nose.tools.eq_(rewritten["trkn"], \ [(episode.EpisodeNumber, len(episode.season))]) nose.tools.eq_(rewritten["stik"], [10]) #nose.tools.eq_(rewritten["hdvd"], self.HD) nose.tools.eq_(rewritten["\xa9gen"], episode.season.show.Genre) us_cert = rewritten.tags["----:com.apple.iTunes:iTunEXTC"][0].decode() nose.tools.eq_(us_cert, 'us-tv|TV-14|500|') #ensures we can parse the embedded XML document plist = rewritten.tags['----:com.apple.iTunes:iTunMOVI'][0] xml = ElementTree.fromstring(plist) nose.tools.eq_(xml.tag, 'plist') #check cover is present covr = rewritten['covr'] nose.tools.eq_(len(covr), 1) covr = covr[0] assert len(covr) != 0
def _downloadAudio(self, video): print("download audio") download_path = os.environ.get('HOME') + '/Downloads/YouTubePlayer/' if not os.path.exists(download_path): print(download_path, "does not exist") os.makedirs(download_path) filepath = download_path + video.title + '[audio].m4a' audio = video.m4astreams[-1] try: audio.download(filepath, quiet=False, callback=self._setdownloadETA) except DownloadError: GObject.idle_add(self.infoLabel.set_text, "Can't download this video.") return metadata = self._getMetadata(video) if metadata is None: return self.infoLabel.set_text('Fixing metadata') audiofile = mp4.MP4(filepath) audiofile['\xa9nam'] = metadata['track_title'] audiofile['\xa9ART'] = metadata['artist'] audiofile['\xa9alb'] = metadata['album'] audiofile['aART'] = metadata['artist'] cover = metadata['album_art_url'] fd = urllib.request.urlopen(cover) covr = mp4.MP4Cover( fd.read(), getattr(mp4.MP4Cover, 'FORMAT_PNG' if cover.endswith('png') else 'FORMAT_JPEG')) fd.close() audiofile['covr'] = [covr] audiofile.save() self.infoLabel.set_text('Metadata fixed')
def mp4info(self): """ MP4(m4a)の曲情報を取得 """ self.tags = mp4.MP4(self.filepath) # 各項目取得 self.title = self.tags.get('\xa9nam', ' ')[0].strip() self.album = self.tags.get('\xa9alb', ' ')[0].strip() self.artist = self.tags.get('\xa9ART', ' ')[0].strip() self.genre = self.tags.get('\xa9gen', ' ')[0].strip() self.comment = self.tags.get('\xa9cmt', ' ')[0].strip() # アートワーク取得 artworks = self.tags.get('covr') # list or None artwork = None if artworks: for artwork in artworks: # 抽出(最後に登録されている画像のみ) pass # bytesへ変換 if artwork: self.artwork = bytes(artwork) else: self.artwork = None
def start(upload_file_path,taskid_re=False): def gene_params(apiname, taskid=None, slice_id=None): ts = str(int(time.time())) m2 = hashlib.md5() m2.update((appid + ts).encode('utf-8')) md5 = m2.hexdigest() # md5 = bytes(md5, encoding='utf-8') md5 = bytes(md5) # 以secret_key为key, 上面的md5为msg, 使用hashlib.sha1加密结果为signa signa = hmac.new(secret_key.encode('utf-8'), md5, hashlib.sha1).digest() signa = base64.b64encode(signa) # signa = str(signa, 'utf-8') signa = str(signa) if not taskid_re: file_len = os.path.getsize(upload_file_path) file_name = os.path.basename(upload_file_path) else: file_len=100 file_name='get_result' param_dict = {} if apiname == api_prepare: # slice_num是指分片数量,如果您使用的音频都是较短音频也可以不分片,直接将slice_num指定为1即可 slice_num = int(file_len / file_piece_sice) + (0 if (file_len % file_piece_sice == 0) else 1) param_dict['app_id'] = appid param_dict['signa'] = signa param_dict['ts'] = ts param_dict['file_len'] = str(file_len) param_dict['file_name'] = file_name param_dict['slice_num'] = str(slice_num) elif apiname == api_upload: param_dict['app_id'] = appid param_dict['signa'] = signa param_dict['ts'] = ts param_dict['task_id'] = taskid param_dict['slice_id'] = slice_id elif apiname == api_merge: param_dict['app_id'] = appid param_dict['signa'] = signa param_dict['ts'] = ts param_dict['task_id'] = taskid param_dict['file_name'] = file_name elif apiname == api_get_progress or apiname == api_get_result: param_dict['app_id'] = appid param_dict['signa'] = signa param_dict['ts'] = ts param_dict['task_id'] = taskid return param_dict # 请求和结果解析,结果中各个字段的含义可参考:https://doc.xfyun.cn/rest_api/%E8%AF%AD%E9%9F%B3%E8%BD%AC%E5%86%99.html def gene_request(apiname, data, files=None, headers=None): response = requests.post(lfasr_host + apiname, data=data, files=files, headers=headers) result = json.loads(response.text) if result["ok"] == 0: print("{} success:".format(apiname) + str(result)) return result else: print("{} error:".format(apiname) + str(result)) exit(0) return result # 预处理 def prepare_request(): return gene_request(apiname=api_prepare, data=gene_params(api_prepare, taskid=None, slice_id=None)) # 上传 def upload_request(taskid, upload_file_path): file_object = open(upload_file_path, 'rb') yield 'uploading file...plz wait DONT CLOSE YOUR BROWSER!!' try: index = 1 sig = SliceIdGenerator() while True: yield 'uploading slice %s ' % str(index) content = file_object.read(file_piece_sice) if not content or len(content) == 0: break files = { "filename": gene_params(api_upload).get("slice_id"), "content": content } response = gene_request(api_upload, data=gene_params(api_upload, taskid=taskid, slice_id=sig.getNextSliceId()), files=files) if response.get('ok') != 0: # 上传分片失败 yield ('upload slice fail, response: ' + str(response)) # return False yield ('upload slice ' + str(index) + ' success') index += 1 finally: 'file index:' + str(file_object.tell()) file_object.close() # return True # 合并 def merge_request(taskid): return gene_request(api_merge, data=gene_params(api_merge, taskid=taskid)) # 获取进度 def get_progress_request(taskid): return gene_request(api_get_progress, data=gene_params(api_get_progress, taskid=taskid)) # 获取结果 def get_result_request(taskid): return gene_request(api_get_result, data=gene_params(api_get_result, taskid=taskid)) if not taskid_re: yield '<h3>1. 预处理,请勿关闭或刷新浏览器</h3> ' pre_result = prepare_request() from mutagen import mp3,mp4 if 'mp3' in upload_file_path[-5:]: audio = mp3.MP3(upload_file_path) audiolength=audio.info.length yield 'filelength: '+str(audio.info.length)+' seconds' if 'm4a' in upload_file_path[-5:]: audio=mp4.MP4(upload_file_path) audiolength = audio.info.length yield 'filelength: '+str(audio.info.length)+' seconds' speed=15 totaltime=float(speed*(float(audiolength)/60)/60) if totaltime == 0:totaltime = 1 yield '预计完成时间: '+str(totaltime)+'分钟' taskid = pre_result["data"] yield '<h3> 2 . 分片上传至服务器,请耐心等待...预计需要:'+str(float(audiolength/10))+'秒</h3> ' for msg in upload_request(taskid=taskid, upload_file_path=upload_file_path): yield msg yield '<h3> 3 . 文件合并</h3>' merge_request(taskid=taskid) yield '<h3> 4 . 获取任务进度 (每10秒刷新一次),taskid: %s </h3>' % str(taskid) def completed_per(pasttime): per=pasttime/(totaltime*60)*100 if per > 99: per = 99.99999 return str(per)+'%' pasttime=0 while True: # 每隔10秒获取一次任务进度 pasttime=pasttime+10 progress = get_progress_request(taskid) progress_dic = progress if progress_dic['err_no'] != 0 and progress_dic['err_no'] != 26605: print('task error: ' + progress_dic['failed']) return else: data = progress_dic['data'] task_status = json.loads(data) if task_status['status'] == 9: # print ('task ' +taskid + ' finished') break yield ('The task is in processing, task status: ' + data+' completed: %s' % completed_per(pasttime)) # yield '# 每次获取进度间隔10S' time.sleep(10) yield '<h3> 5 . 完成转写</h3>' yield get_result_request(taskid=taskid) else: yield get_result_request(taskid=taskid_re)
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 setAlbumInfo(album): conn = sqlite3.connect('database/music.db') # print("Connection granted") cur = conn.cursor() conn_settings = sqlite3.connect('database/settings.db') # print("Connection granted") cur_settings = conn_settings.cursor() cur_settings.execute("SELECT music_directory_path FROM music_directory WHERE active='True'") result_directory = cur_settings.fetchone() cur.execute("SELECT album_artist FROM album WHERE album_spotify_id='%s'" % str(album)) result_art_id = cur.fetchone() cur.execute("SELECT artist_name FROM artist WHERE artist_id='%s'" % str(result_art_id[0])) result_art_name = cur.fetchone() cur.execute("SELECT album_name FROM album WHERE album_spotify_id='%s'" % str(album)) result_alb_name = cur.fetchone() cur.execute("SELECT album_id FROM album WHERE album_spotify_id='%s'" % str(album)) result_alb_id = cur.fetchone() cur.execute("SELECT album_number_songs FROM album WHERE album_spotify_id='%s'" % str(album)) result_alb_number_songs = cur.fetchone() print(result_alb_number_songs[0]) cur.execute("SELECT album_coverart FROM album WHERE album_spotify_id='%s'" % str(album)) result_alb_coverart = cur.fetchone() try: coverArtDirectory = "%s%s/cover_art" % (str(result_directory[0]), str(result_art_name[0])) coverArtName = "%s.jpg" % str(result_alb_name[0]) if not os.path.exists(coverArtDirectory): os.makedirs(coverArtDirectory) if coverArtName not in coverArtDirectory: urllib.request.urlretrieve("%s" % str(result_alb_coverart[0]), "%s/%s" % (str(coverArtDirectory), str(coverArtName))) print("Cover Art Downloaded Fine!") directory = "%s%s/%s" % (str(result_directory[0]), str(result_art_name[0]), str(result_alb_name[0])) directoryResults = os.listdir(directory) for re in directoryResults: print("PreCleaned: " + re) cleanResult = re.replace('.m4a', '') print("Cleaned: " + cleanResult) cur.execute("SELECT song_name FROM song WHERE song_album='%s' AND song_name='%s'" % (str(result_alb_id[0]), str(cleanResult))) result_song_name = cur.fetchone() cur.execute("SELECT song_track_number FROM song WHERE song_album='%s' AND song_name='%s'" % (str(result_alb_id[0]), str(cleanResult))) result_song_track_number = cur.fetchone() cur.execute("SELECT song_spotify_id FROM song WHERE song_album='%s' AND song_name='%s'" % (str(result_alb_id[0]), str(cleanResult))) result_song_spotify_id = cur.fetchone() if (result_song_track_number != None): # print(str(result_song_track_number[0])) coverArtLocation = "%s/%s" % (str(coverArtDirectory), str(coverArtName)) song = mutagen.MP4("%s/%s" % (str(directory), str(re))) song['\xa9nam'] = result_song_name[0] song['\xa9alb'] = result_alb_name[0] song['\xa9ART'] = result_art_name[0] song['trkn'] = [(int(result_song_track_number[0]), int(result_alb_number_songs[0]))] with open(coverArtLocation, "rb") as f: song["covr"] = [mutagen.MP4Cover(f.read(), imageformat=mutagen.MP4Cover.FORMAT_JPEG)] song.save() except FileNotFoundError as err: print("Sorry File Not found: {0}".format(err)) # # savedir = "/home/que/Music/MusicLibrary/Artists/%s/%s/" % (str(art_id[0]), str(alb_name[0])) # savename = "%s%s" % (fileName, audioType) conn.close() conn_settings.close()
def get_tags(song_path): return mp4.MP4('/Users/ft3/Music/lofi/{}'.format(song_path))
def mp4Tagger(file, metaData): ''' Name: mp4Tags Purpose: A function to parse information from the IMDbPY API and write Tag data to MP4 files. Inputs: file : Full path of file to write metadata to. metaData : Dictionary of meta data where keys are internal metadata keys and values are metadata values Outputs: Returns following values based on completion. 0 : Completed successfully. 1 : Input was NOT and MP4 2 : IMDb ID was not valid 3 : Failed to download information from IMDb AND themoviedb.org 4 : Writing tags is NOT possible 5 : Failed when trying to remove tags from file. 6 : Failed when trying to write tags to file. 10 : IMDbPY not installed AND getTMDb_Info failed to import 11 : File is too large Keywords: None Dependencies: mutagen Author and History: Kyle R. Wodzicki Created 18 Feb. 2018 ''' log = logging.getLogger(__name__) # Set up a logger log.debug('Testing file is MP4') # Debugging information if not file.endswith('.mp4'): # If the input file does NOT end in '.mp4' log.error('Input file is NOT an MP4!!!') return 1 # Print message and return code one (1) log.debug('Testing file too large') # Debugging information if os.stat( file ).st_size > sys.maxsize: # If the file size is larger than the supported maximum size log.error('Input file is too large!') return 11 # Print message and return code eleven (11) metaData = toMP4(metaData) if len(metaData) == 0: log.warning('No metadata, cannot write tags') return 3 filedir, filebase = os.path.dirname(file), os.path.basename( file) # Get the directory and baseanem of the file log.debug('Loading file using mutagen.mp4.MP4') # Debugging information handle = mp4.MP4(file) # Initialize mutagen MP4 handler log.debug('Attempting to add tag block to file') # Debugging information try: # Try to... handle.add_tags() # Add new tags to the file except mp4.error as e: # On exception, catch the error if 'already exists' in e.__str__( ): # If the error is that the tag block already exists log.debug( 'MP4 tags already exist in file.') # Debugging information pass # Pass else: # Else, adding is not possible log.error('Could NOT add tags to file!') # Log an error return 4 # Return code 4 try: # Try to... handle.delete() # Remove all old tags. except mp4.error as e: # On exception, catch the error log.error(e.__str__()) # Log the error return 5 # Return code 5 log.debug('Setting basic inforamtion') # Debugging information for key, val in metaData.items(): if key == 'covr' and val != '': log.debug('Attempting to get coverart') # Debugging information fmt = mp4.AtomDataType.PNG if val.endswith( 'png') else mp4.AtomDataType.JPEG # Set format for the image data = download(val) if data is not None: val = [mp4.MP4Cover(data, fmt)] # Add image to file else: continue try: handle[key] = val except: log.warning('Failed to write metadata for: {}'.format(key)) log.debug('Saving tags to file') # Debugging information try: # Try to... handle.save() # Save the tags except: # On exception log.error('Failed to save tags to file!') # Log an error return 6 return 0
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 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(enc_str(audio_file))[ST_SIZE]) + " ]" + Fore.RESET) if args.output_type == "wav" or args.output_type == "pcm": print_yellow("Skipping metadata tagging for " + args.output_type + " encoding...") return # ensure everything is loaded still if not track.is_loaded: track.load(args.timeout) if not track.album.is_loaded: track.album.load(args.timeout) # use mutagen to update audio file tags try: audio = None tags = None if args.output_type == "flac": audio = flac.FLAC(audio_file) tags = VorbisTags(args, audio_file, idx, track, ripper) tags.set_tags(audio) elif args.output_type == "aiff": audio = aiff.AIFF(audio_file) tags = Id3Tags(args, audio_file, idx, track, ripper) tags.set_tags(audio) elif args.output_type == "ogg": audio = oggvorbis.OggVorbis(audio_file) tags = VorbisTags(args, audio_file, idx, track, ripper) tags.set_tags(audio) elif args.output_type == "opus": audio = oggopus.OggOpus(audio_file) tags = VorbisTags(args, audio_file, idx, track, ripper) tags.set_tags(audio) elif args.output_type == "aac": audio = aac.AAC(audio_file) tags = RawId3Tags(args, audio_file, idx, track, ripper) tags.set_tags(audio) elif args.output_type == "m4a" or args.output_type == "alac.m4a": if sys.version_info >= (3, 0): audio = mp4.MP4(audio_file) tags = MP4Tags(args, audio_file, idx, track, ripper) tags.set_tags(audio) else: audio = m4a.M4A(audio_file) tags = M4ATags(args, audio_file, idx, track, ripper) tags.set_tags(audio) audio = mp4.MP4(audio_file) elif args.output_type == "mp3": audio = mp3.MP3(audio_file, ID3=id3.ID3) tags = Id3Tags(args, audio_file, idx, track, ripper) tags.set_tags(audio) # utility functions 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"] return modes[mode] if mode < len(modes) else "" def channel_str(num): channels = ["", "Mono", "Stereo"] return channels[num] if num < len(channels) else "" def print_line(): print("-" * 79) # log tags print_line() print_yellow("Setting artist: " + tags.artists()) if tags.album() is not None: print_yellow("Setting album: " + tags.album()) if tags.album_artist() is not None: print_yellow("Setting album artist: " + tags.album_artist()) print_yellow("Setting title: " + tags.title()) print_yellow("Setting track info: (" + tags.track_idx() + ", " + tags.num_tracks() + ")") print_yellow("Setting disc info: (" + tags.disc_idx() + ", " + tags.num_discs() + ")") print_yellow("Setting release year: " + tags.year()) if tags.genres() is not None: print_yellow("Setting genres: " + " / ".join(tags.genres())) if tags.image is not None: print_yellow("Adding cover image") if tags.comment() is not None: print_yellow("Adding comment: " + tags.comment()) if tags.grouping() is not None: print_yellow("Adding grouping: " + tags.grouping()) # log bitrate info 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_line() print_yellow("Writing Vorbis comments - " + audio.tags.vendor) print_line() elif args.output_type == "aiff": print("Time: " + format_time(audio.info.length) + "\tAudio Interchange File Format" + "\t[ " + bit_rate_str(audio.info.bitrate / 1000) + " @ " + str(audio.info.sample_rate) + " Hz - " + channel_str(audio.info.channels) + " ]") print_line() 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) print_line() elif 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_line() print_yellow("Writing Apple iTunes metadata") print_line() 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_line() print_yellow("Writing Vorbis comments - " + audio.tags.vendor) print_line() elif args.output_type == "opus": print("Time: " + format_time(audio.info.length) + "\tOpus Codec" + "\t[ " + channel_str(audio.info.channels) + " ]") print_line() print_yellow("Writing Vorbis comments - " + audio.tags.vendor) print_line() 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_line() id3_version = "v%d.%d" % (audio.tags.version[0], audio.tags.version[1]) print("ID3 " + id3_version + ": " + str(len(audio.tags.values())) + " frames") print_yellow("Writing ID3 version " + id3_version) print_line() 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_line() id3_version = "v%d.%d" % (audio.tags.version[0], audio.tags.version[1]) print("ID3 " + id3_version + ": " + str(len(audio.tags.values())) + " frames") print("Writing ID3 version " + id3_version) print_line() 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_line() print_yellow("Writing Apple iTunes metadata - " + str(audio.info.codec)) print_line() except id3.error: print_yellow("Warning: exception while saving id3 tag: " + str(id3.error))
def mp4Tagger( file, metaData ): """ Parse information from the IMDbPY API and write Tag data to MP4 files. Arguments: file (str): Full path of file to write metadata to. metaData (dict): Dictionary of meta data where keys are internal metadata keys and values are metadata values Keyword arguments: None Returns: int: Returns following values based on completion. - 0 : Completed successfully. - 1 : Input was NOT and MP4 - 2 : IMDb ID was not valid - 3 : Failed to download information from IMDb AND themoviedb.org - 4 : Writing tags is NOT possible - 5 : Failed when trying to remove tags from file. - 6 : Failed when trying to write tags to file. - 10 : IMDbPY not installed AND getTMDb_Info failed to import - 11 : File is too large """ log = logging.getLogger(__name__) # Set up a logger log.debug( 'Testing file is MP4' ) # Debugging information if not file.endswith('.mp4'): # If the input file does NOT end in '.mp4' log.error('Input file is NOT an MP4!!!') return 1 # Print message and return code one (1) log.debug( 'Testing file too large' ) # Debugging information if os.stat(file).st_size > sys.maxsize: # If the file size is larger than the supported maximum size log.error('Input file is too large!') return 11 # Print message and return code eleven (11) version = metaData.pop('version', '') comment = _updateComment( metaData.get('comment', '') ) metaData.update( {'comment' : comment } ) metaData = toMP4(metaData) if len(metaData) == 0: log.warning('No metadata, cannot write tags') return 3 filedir, filebase = os.path.dirname( file ), os.path.basename( file ) # Get the directory and baseanem of the file log.debug('Loading file using mutagen.mp4.MP4') # Debugging information handle = mp4.MP4(file) # Initialize mutagen MP4 handler log.debug('Attempting to add tag block to file') # Debugging information try: # Try to... handle.add_tags() # Add new tags to the file except mp4.error as e: # On exception, catch the error if 'already exists' in e.__str__(): # If the error is that the tag block already exists log.debug('MP4 tags already exist in file.') # Debugging information pass # Pass else: # Else, adding is not possible log.error('Could NOT add tags to file!') # Log an error return 4 # Return code 4 try: # Try to... handle.delete(); # Remove all old tags. except mp4.error as e: # On exception, catch the error log.error( e.__str__() ); # Log the error return 5; # Return code 5 log.debug('Setting basic inforamtion') # Debugging information for key, val in metaData.items(): if key == 'covr' and val != '': fmt = mp4.AtomDataType.PNG if val.endswith('png') else mp4.AtomDataType.JPEG # Set format for the image if os.path.isfile( val ): # If value is local file with open(val, 'rb') as fid: data = fid.read() # Read in data from file else: log.debug('Attempting to get coverart') # Debugging information _, data = downloadCover( file, val, text = version ) # Download the data if data is not None: val = [ mp4.MP4Cover( data, fmt ) ] # Add image to file else: continue try: handle[key] = val except: log.warning( 'Failed to write metadata for: {}'.format(key) ) log.debug('Saving tags to file') # Debugging information try: # Try to... handle.save(); # Save the tags except: # On exception log.error('Failed to save tags to file!'); # Log an error return 6 return 0
def set_metadata_tags(args, audio_file, idx, track, ripper): if args.output_type == "wav" or args.output_type == "pcm": print(Fore.YELLOW + "Skipping metadata tagging for " + args.output_type + " encoding..." + Fore.RESET) return # ensure everything is loaded still if not track.is_loaded: track.load(args.timeout) if not track.album.is_loaded: track.album.load(args.timeout) album_browser = track.album.browse() album_browser.load(args.timeout) # 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, 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) artists = ", ".join([artist.name for artist in track.artists]) \ if args.all_artists else track.artists[0].name artists_ascii = to_ascii(artists, on_error) album_artist = to_ascii(track.album.artist.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, idx, track) comment_ascii = to_ascii(comment, on_error) if args.grouping is not None: grouping = \ format_track_string(ripper, args.grouping, 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 image = None if args.large_cover_art: image = ripper.web.get_large_coverart(track.link.uri) # if we fail, use regular cover size if image is None: image = track.album.cover() if image is not None: image.load(args.timeout) image = image.data 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(enc_str(cover_file), "wb") as f: f.write(image) if args.cover_file is not None: write_image(args.cover_file) elif args.cover_file_and_embed is not None: write_image(args.cover_file_and_embed) embed_image_func(image) else: embed_image_func(image) def set_id3_tags(audio): # add ID3 tag if it doesn't exist audio.add_tags() def embed_image(data): audio.tags.add( id3.APIC(encoding=3, mime='image/jpeg', type=3, desc='Front Cover', data=data)) 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(artists, artists_ascii)], encoding=3)) if album_artist is not None: audio.tags.add( id3.TPE2(text=[ tag_to_ascii(track.album.artist.name, album_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(data): id3_dict.add( id3.APIC(encoding=3, mime='image/jpeg', type=3, desc='Front Cover', data=data)) 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(artists, artists_ascii)], encoding=3)) if album_artist is not None: id3_dict.add( id3.TPE2(text=[ tag_to_ascii(track.album.artist.name, album_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(data): pic = flac.Picture() pic.type = 3 pic.mime = "image/jpeg" pic.desc = "Front Cover" pic.data = data 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(artists, artists_ascii) if album_artist is not None: audio.tags["ALBUMARTIST"] = \ tag_to_ascii(track.album.artist.name, album_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(data): audio.tags["covr"] = mp4.MP4Cover(data) 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(artists, artists_ascii) if album_artist is not None: audio.tags["aART"] = \ tag_to_ascii(track.album.artist.name, album_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(data): audio.tags[str("covr")] = m4a.M4ACover(data) 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(artists, artists_ascii) if album_artist is not None: audio.tags[str("aART")] = tag_to_ascii(track.album.artist.name, album_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) if args.output_type == "aiff": audio = aiff.AIFF(audio_file) set_id3_tags(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": audio = mp4.MP4(audio_file) set_mp4_tags(audio) elif args.output_type == "alac.m4a": audio = mp4.MP4(audio_file) set_mp4_tags(audio) 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(Fore.YELLOW + " Artist:\t" + Fore.RESET + artists_ascii) if album is not None: print(Fore.YELLOW + " Album:\t" + Fore.RESET + album) if album_artist is not None: print(Fore.YELLOW + " Album artist:\t" + Fore.RESET + album_artist) print(Fore.YELLOW + " Title:\t" + Fore.RESET + title) print(Fore.YELLOW + " Track number:\t" + Fore.RESET + str(track.index) + "/" + str(num_tracks)) print(Fore.YELLOW + " Disc number:\t" + Fore.RESET + str(track.disc) + "/" + str(num_discs)) print(Fore.YELLOW + " Year:\t\t" + Fore.RESET + str(track.album.year)) if genres is not None and genres: print(Fore.YELLOW + " Genres:\t" + Fore.RESET + " / ".join(genres_ascii)) if image is not None: print(Fore.YELLOW + " Cover image:\t" + Fore.RESET + "Yes") if args.comment is not None: print(Fore.YELLOW + " Comment:\t" + Fore.RESET + comment_ascii) if args.grouping is not None: print(Fore.YELLOW + " Grouping:\t" + Fore.RESET + grouping_ascii) print(Fore.YELLOW + " Time:\t\t" + Fore.RESET + format_time(audio.info.length)) print(Fore.YELLOW + " Size:\t\t" + Fore.RESET + format_size(os.stat(enc_str(audio_file))[ST_SIZE])) if args.output_type == "flac": print(Fore.YELLOW + " Format:\t" + Fore.RESET + "Free Lossless Audio Codec") bit_rate = ((audio.info.bits_per_sample * audio.info.sample_rate) * audio.info.channels) print(Fore.YELLOW + " Quality:\t" + Fore.RESET + bit_rate_str(bit_rate / 1000) + " @ " + str(audio.info.sample_rate) + " Hz - " + channel_str(audio.info.channels)) print(Fore.YELLOW + " Comment:\t" + Fore.RESET + audio.tags.vendor) if args.output_type == "aiff": print(Fore.YELLOW + " Format:\t" + Fore.RESET + "Audio Interchange File Format") print(Fore.YELLOW + " Quality:\t" + Fore.RESET + bit_rate_str(audio.info.bitrate / 1000) + " @ " + str(audio.info.sample_rate) + " Hz - " + channel_str(audio.info.channels)) id3_version = "v%d.%d" % (audio.tags.version[0], audio.tags.version[1]) print(Fore.YELLOW + " ID3:\t\t" + Fore.RESET + id3_version + " - " + str(len(audio.tags.values())) + " frames") elif args.output_type == "alac.m4a": print(Fore.YELLOW + " Format:\t" + Fore.RESET + "Apple Lossless") bit_rate = ((audio.info.bits_per_sample * audio.info.sample_rate) * audio.info.channels) print(Fore.YELLOW + " Quality:\t" + Fore.RESET + bit_rate_str(bit_rate / 1000) + " @ " + str(audio.info.sample_rate) + " Hz - " + channel_str(audio.info.channels)) print(Fore.YELLOW + " iTunes:\t" + Fore.RESET + str(audio.info.codec)) elif args.output_type == "ogg": print(Fore.YELLOW + " Format:\t" + Fore.RESET + "Ogg Vorbis Codec") print(Fore.YELLOW + " Quality:\t" + Fore.RESET + bit_rate_str(audio.info.bitrate / 1000) + " @ " + str(audio.info.sample_rate) + " Hz - " + channel_str(audio.info.channels)) print(Fore.YELLOW + " Comment:\t" + Fore.RESET + audio.tags.vendor) elif args.output_type == "opus": print(Fore.YELLOW + " Format:\t" + Fore.RESET + "Opus Codec") print(Fore.YELLOW + " Quality:\t" + Fore.RESET + channel_str(audio.info.channels)) print(Fore.YELLOW + " Comment:\t" + Fore.RESET + audio.tags.vendor) elif args.output_type == "mp3": print(Fore.YELLOW + " Format:\t" + Fore.RESET + "MPEG" + str(audio.info.version) + ", Layer " + ("I" * audio.info.layer)) print(Fore.YELLOW + " Quality:\t" + Fore.RESET + bit_rate_str(audio.info.bitrate / 1000) + " @ " + str(audio.info.sample_rate) + " Hz - " + mode_str(audio.info.mode)) id3_version = "v%d.%d" % (audio.tags.version[0], audio.tags.version[1]) print(Fore.YELLOW + " ID3:\t\t" + Fore.RESET + id3_version + " - " + str(len(audio.tags.values())) + " frames") elif args.output_type == "aac": print(Fore.YELLOW + " Format:\t" + Fore.RESET + "Advanced Audio Coding") print(Fore.YELLOW + " Quality:\t" + Fore.RESET + bit_rate_str(audio.info.bitrate / 1000) + " @ " + str(audio.info.sample_rate) + " Hz - " + channel_str(audio.info.channels)) id3_version = "v%d.%d" % (audio.tags.version[0], audio.tags.version[1]) print(Fore.YELLOW + " ID3:\t\t" + Fore.RESET + id3_version + " - " + str(len(audio.tags.values())) + " frames") elif args.output_type == "m4a": print(Fore.YELLOW + " Format:\t" + Fore.RESET + "MPEG-4 Part 14 Audio") bit_rate = ((audio.info.bits_per_sample * audio.info.sample_rate) * audio.info.channels) print(Fore.YELLOW + " Quality:\t" + Fore.RESET + bit_rate_str(bit_rate / 1000) + " @ " + str(audio.info.sample_rate) + " Hz - " + channel_str(audio.info.channels)) print(Fore.YELLOW + " iTunes:\t" + Fore.RESET + str(audio.info.codec)) except id3.error: print(Fore.YELLOW + "Warning: exception while saving id3 tag: " + str(id3.error) + Fore.RESET)