def test_hash(self): frame = COMM(encoding=0, lang="foo", desc="d") self.assertEqual(frame.HashKey, "COMM:d:foo") frame._pprint() self.assertEquals(COMM(text="a").HashKey, COMM(text="b").HashKey) self.assertNotEquals(COMM(desc="a").HashKey, COMM(desc="b").HashKey) self.assertNotEquals( COMM(lang="abc").HashKey, COMM(lang="def").HashKey)
def copyTagsToTranscodedFileMp3(losslessFile, lossyFile): # # Copy the tags from the losslessFile (.flac) to the lossyFile. # All previous tags from the lossyFile will be deleted before the # tags from the losslessFile are copied. # from mutagen.flac import FLAC from mutagen.id3 import ID3 # Read all tags from the flac file flacFile = FLAC(losslessFile) flacFileTags = flacFile.tags # Returns a dictionary containing the flac tags # Only mp3 files with ID3 headers can be openend. # So be sure to add some tags during encoding .wav. to mp3 # Mapping from Vorbis comments field recommendations to id3v2_4_0 # For more information about vorbis field recommendations: http://reactor-core.org/ogg-tagging.html # For more information about id3v2_4_0 frames: http://www.id3.org/id3v2.4.0-frames # # Single value tags: # ALBUM -> TALB # ARTIST -> TPE1 # PUBLISHER -> TPUB # COPYRIGHT -> WCOP # DISCNUMBER -> TPOS # ISRC -> TSRC # EAN/UPN # LABEL # LABELNO # LICENSE -> TOWN # OPUS -> TIT3 # SOURCEMEDIA -> TMED # TITLE -> TIT2 # TRACKNUMBER -> TRCK # VERSION # ENCODED-BY -> TENC # ENCODING # Multiple value tags: # COMPOSER -> TCOM # ARRANGER # LYRICIST -> TEXT # AUTHOR -> TEXT # CONDUCTOR -> TPE3 # PERFORMER -> # ENSEMBLE -> TPE2 # PART -> TIT1 # PARTNUMBER -> TIT1 # GENRE -> TCON # DATE -> TDRC # LOCATION # COMMENT -> COMM # Other vorbis tags are mapped to TXXX tags mp3File = ID3(lossyFile) mp3File.delete() for key, value in flacFileTags.items(): if key == 'title': # Map to TIT2 frame from mutagen.id3 import TIT2 mp3File.add(TIT2(encoding=3, text=value)) elif key == 'album': # Map to TALB frame from mutagen.id3 import TALB mp3File.add(TALB(encoding=3, text=value)) elif key == 'artist': # Map to TPE1 frame from mutagen.id3 import TPE1 mp3File.add(TPE1(encoding=3, text=value)) elif key == 'tracknumber': # Map to TRCK frame from mutagen.id3 import TRCK mp3File.add(TRCK(encoding=3, text=value)) elif key == 'date': # Map to TDRC frame from mutagen.id3 import TDRC mp3File.add(TDRC(encoding=3, text=value)) elif key == 'genre': # Map to TCON frame from mutagen.id3 import TCON mp3File.add(TCON(encoding=3, text=value)) elif key == 'discnumber': # Map to TPOS frame from mutagen.id3 import TPOS mp3File.add(TPOS(encoding=3, text=value)) elif key == 'composer': # Map to TCOM frame from mutagen.id3 import TCOM mp3File.add(TCOM(encoding=3, text=value)) elif key == 'conductor': # Map to TPE3 frame from mutagen.id3 import TPE3 mp3File.add(TPE3(encoding=3, text=value)) elif key == 'ensemble': # Map to TPE2 frame from mutagen.id3 import TPE2 mp3File.add(TPE2(encoding=3, text=value)) elif key == 'comment': # Map to COMM frame from mutagen.id3 import COMM mp3File.add(COMM(encoding=3, text=value)) elif key == 'publisher': # Map to TPUB frame from mutagen.id3 import TPUB mp3File.add(TPUB(encoding=3, text=value)) elif key == 'opus': # Map to TIT3 frame from mutagen.id3 import TIT3 mp3File.add(TIT3(encoding=3, text=value)) elif key == 'sourcemedia': # Map to TMED frame from mutagen.id3 import TMED mp3File.add(TMED(encoding=3, text=value)) elif key == 'isrc': # Map to TSRC frame from mutagen.id3 import TSRC mp3File.add(TSRC(encoding=3, text=value)) elif key == 'license': # Map to TOWN frame from mutagen.id3 import TOWN mp3File.add(TOWN(encoding=3, text=value)) elif key == 'copyright': # Map to WCOP frame from mutagen.id3 import WCOP mp3File.add(WCOP(encoding=3, text=value)) elif key == 'encoded-by': # Map to TENC frame from mutagen.id3 import TENC mp3File.add(TENC(encoding=3, text=value)) elif (key == 'part' or key == 'partnumber'): # Map to TIT3 frame from mutagen.id3 import TIT3 mp3File.add(TIT3(encoding=3, text=value)) elif (key == 'lyricist' or key == 'textwriter'): # Map to TEXT frame from mutagen.id3 import TIT3 mp3File.add(TIT3(encoding=3, text=value)) else: #all other tags are mapped to TXXX frames # Map to TXXX frame from mutagen.id3 import TXXX mp3File.add(TXXX(encoding=3, text=value, desc=key)) mp3File.update_to_v24() mp3File.save() return
def test_make_from_empty(self): empty = b'TAG' + b'\x00' * 124 + b'\xff' self.assertEquals(MakeID3v1({}), empty) self.assertEquals(MakeID3v1({'TCON': TCON()}), empty) self.assertEquals(MakeID3v1({'COMM': COMM(encoding=0, text="")}), empty)
def test_multi_COMM(self): from mutagen.id3 import COMM self.assertEquals(COMM(encoding=0, text="a").HashKey, COMM(encoding=0, text="b").HashKey) self.assertNotEquals(COMM(encoding=0, desc="a").HashKey, COMM(encoding=0, desc="b").HashKey) self.assertNotEquals( COMM(lang="abc").HashKey, COMM(lang="def").HashKey)
class MediaHelper: config = None tagSep = None maxTags = None overwriteFields = None forceOverwriteFields = None id3v1Handling = None useBothArtistFields = False artistFieldPref = [] formatFieldMap = dict(id3=dict(genre='TCON', grouping='TIT1', comment="COMM::'eng'", artist='TPE1', albumartist='TPE2', album='TALB', track='TIT2'), mp4=dict(genre='\xa9gen', grouping='\xa9grp', comment='\xa9cmt', artist='\xa9ART', albumartist='aART', album='\xa9alb', track='\xa9nam'), oggvorbis=dict(genre='genre', grouping='grouping', comment='comment', artist='artist', albumartist='album artist', album='album', track='title'), flac=dict(genre='genre', grouping='grouping', comment='comment', artist='artist', albumartist='album artist', album='album', track='title')) id3FuncMap = dict( genre=lambda val: TCON(encoding=3, text=val), grouping=lambda val: TIT1(encoding=3, text=val), comment=lambda val: COMM(encoding=3, lang='eng', desc='', text=val), artist=lambda val: TPE1(encoding=3, text=val), albumartist=lambda val: TPE2(encoding=3, text=val), album=lambda val: TALB(encoding=3, text=val), track=lambda val: TIT2(encoding=3, text=val)) meaninglessArtists = frozenset([ 'various artists', 'soundtrack', 'soundtracks', 'original soundtrack', 'ost', 'compilation' ]) def __init__(self, config): self.config = config self.tagSep = self.config.get('tagSep') if (len(self.tagSep.strip()) > 0): self.tagSep += ' ' self.maxTags = dict(genre=self.config.getint('genreMaxTags'), grouping=self.config.getint('groupingMaxTags'), comment=self.config.getint('commentMaxTags')) self.overwriteFields = set( map(string.strip, self.config.get('overwriteFields').lower().split(','))) self.forceOverwriteFields = set( map(string.strip, self.config.get('forceOverwriteFields').lower().split(','))) self.id3v1Handling = self.config.getint('id3v1Handling') self.artistFieldPref = ['albumartist', 'artist'] if (self.config.get('artistField').lower() == 'both'): self.useBothArtistFields = True elif (self.config.get('artistField').lower() == 'artist'): self.artistFieldPref.reverse() def getMediawrapper(self, filename): root, ext = os.path.splitext(filename.lower()) if (ext == '.mp3'): mediawrapper = ID3(filename) elif (ext == '.m4a'): mediawrapper = MP4(filename) elif (ext == '.ogg'): mediawrapper = OggVorbis(filename) elif (ext == '.flac'): mediawrapper = FLAC(filename) else: mediawrapper = mutagen.File(filename) return mediawrapper def extractMetadata(self, filename): try: mediawrapper = self.getMediawrapper(filename) if (isinstance(mediawrapper, ID3)): return self.extractMetadataHelper(mediawrapper, self.formatFieldMap['id3'], filename) elif (isinstance(mediawrapper, MP4)): return self.extractMetadataHelper(mediawrapper, self.formatFieldMap['mp4'], filename) elif (isinstance(mediawrapper, OggVorbis)): return self.extractMetadataHelper( mediawrapper, self.formatFieldMap['oggvorbis'], filename) elif (isinstance(mediawrapper, FLAC)): return self.extractMetadataHelper(mediawrapper, self.formatFieldMap['flac'], filename) else: if (self.config.getboolean('verbose')): common.safeStdout( '\tSkipping unknown/incompatible media file type [' + filename + ']') except Exception, err: common.safeStderr('Error seen during media reading: ' + str(err)) return None
def download(self): """ Starts the download process for this track. Also writes the file and applies ID3 tags if specified. Requires the track to have been prepared by the prepare method beforehand. """ if not self.album: safe_print('\nWriting file to {}'.format(self.output)) # Clean up the main title. if not self.short: clean_title = format_information(self.title, self.artist, self.album, self.index) else: clean_title = short_information(self.title, self.index) # Download the file. status = download_file(self.mp3_url, self.output, clean_title + ".mp3", verbose=self.verbose, silent=self.silent, sleep=self.sleep) # Abort further processes if we receive an error status code. if not status or status > 2: if not self.silent: print('\nFailed to download the file. Error code {}'.format( status)) return status # Write ID3 tags if the id3_enabled is true. if self.id3_enabled: # Fix ID3 tags. Create ID3 tags if not present. try: tags = ID3( os.path.join(self.output, safe_filename(clean_title + ".mp3"))) except ID3NoHeaderError: tags = ID3() # Title and artist tags. Split the title if it contains the artist tag. if " - " in self.title: split_title = str(self.title).split(" - ", 1) tags["TPE1"] = TPE1(encoding=3, text=str(split_title[0])) tags["TIT2"] = TIT2(encoding=3, text=str(split_title[1])) else: tags["TIT2"] = TIT2(encoding=3, text=str(self.title)) tags["TPE1"] = TPE1(encoding=3, text=str(self.artist)) # Album tag. Make sure we have it. if self.album: tags["TALB"] = TALB(encoding=3, text=str(self.album)) # Track index tag. if self.index: tags["TRCK"] = TRCK(encoding=3, text=str(self.index)) # Track date. if self.date: tags["TDRC"] = TDRC(encoding=3, text=str(self.date)) # Album artist if not self.album_artist: self.album_artist = self.artist tags["TPE2"] = TPE2(encoding=3, text=str(self.album_artist)) # Retrieve the base page URL. base_url = "{}//{}".format( str(self.url).split("/")[0], str(self.url).split("/")[2]) # Add the Bandcamp base comment in the ID3 comment tag. tags["COMM"] = COMM(encoding=3, lang='XXX', desc=u'', text=u'Visit {}'.format(base_url)) # Save all tags to the track. tags.save( os.path.join(self.output, safe_filename(clean_title + ".mp3"))) # Download artwork if it is enabled. if self.art_enabled: status = download_file(self.art_url, self.output, clean_title + self.art_url[-4:]) if status == 1: if self.verbose: safe_print('\nSaved track art to {}{}{}'.format( self.output, clean_title, self.art_url[-4:])) elif status == 2: if self.verbose: print('\nArtwork already found.') elif not self.silent: print('\nFailed to download the artwork. Error code {}'.format( status))
def test_COMM(self): from mutagen.id3 import COMM frame = COMM(encoding=0, lang="foo", desc="d") self.assertEqual(frame.HashKey, "COMM:d:foo") frame._pprint()
def set_comment(filename, comment): audio = ID3(filename) audio.add(COMM(encoding=3, text=comment)) audio.save()
def download(broadcast, targetDir, reliveUrlTemplate): broadcastStartDT = parse(broadcast['start']) broadcastEndDT = parse(broadcast['end']) # build filename from channel, show title and broadcast datetime, while escaping "bad" characters filename = os.path.join( targetDir, re.sub( '[^\w\s\-\.\[\]]', '_', broadcast['trackingInfos']['pageVars']['broadcast_service'] + ' ' + broadcastStartDT.astimezone( pytz.timezone('Europe/Berlin')).strftime("%Y-%m-%d %H:%M") + ' ' + broadcast['trackingInfos']['pageVars']['topline']) + ".mp3") # skip broadcast if file is already exists if os.path.isfile(filename) and os.path.getsize(filename) > 0: print("%s already exists, skipping." % filename, flush=True) return # get links to all audio segments of this broadcast segmentUrls = getSegmentUrls(broadcastStartDT, broadcastEndDT, reliveUrlTemplate) if segmentUrls is None: # skip broadcast if no segments available print("Skipping %s, not yet in relive" % filename) return # dowload all ts segments, and convert them to mp3 print("Downloading %s ..." % filename, end=" ", flush=True) try: sound = AudioSegment.empty() for i in segmentUrls: sound += AudioSegment.from_file(BytesIO(urlopen(i).read())) sound.export(filename, format="mp3") except: print("failed.", flush=True) return else: print("done.", flush=True) # ID3: remove all tags try: tags = ID3(filename) tags.delete() except ID3NoHeaderError: tags = ID3() # ID3: save as much information as possible in the ID3 tags tags.add( TRSN( text=[broadcast['trackingInfos']['pageVars']['broadcast_service'] ])) tags.add( TPE1( text=[broadcast['trackingInfos']['pageVars']['broadcast_service'] ])) tags.add( TALB(text=[ " - ".join( list( dict.fromkeys([ broadcast['trackingInfos']['pageVars']['topline'], broadcast['trackingInfos']['pageVars']['title'] ]))) ])) tags.add(TRCK(text=['1/1'])) #tags.add(TIT2(text=[broadcastStartDT.astimezone(pytz.timezone('Europe/Berlin')).strftime("%Y-%m-%d %H:%M")])) tags.add(TIT2(text=[broadcast['publicationOf']['title']])) tags.add( COMM(lang="deu", desc="desc", text=[broadcast['publicationOf']['description']])) tags.add( TYER(text=[ broadcastStartDT.astimezone(pytz.timezone( 'Europe/Berlin')).strftime("%Y") ])) tags.add( TDAT(text=[ broadcastStartDT.astimezone(pytz.timezone( 'Europe/Berlin')).strftime("%d%m") ])) tags.add( TIME(text=[ broadcastStartDT.astimezone(pytz.timezone( 'Europe/Berlin')).strftime("%H%M") ])) tags.add( TLEN(text=[ int((broadcastEndDT - broadcastStartDT).total_seconds() * 1000) ])) tags.add(WOAS(url=broadcast['publicationOf']['canonicalUrl'])) tags.add(WORS(url="https://www.br.de/radio/")) # ID3: chapters chapterNr = 0 for chapter in broadcast['items']: chapterStartDT = parse(chapter['start']) if 'duration' in chapter and chapter['duration'] is not None: chapterEndDT = chapterStartDT + timedelta( seconds=chapter['duration']) else: chapterEndDT = broadcastEndDT artists = [] for i in ['performer', 'author']: if i in chapter and chapter[i] is not None and len(chapter[i]) > 0: artists.append(chapter[i]) titles = [] for i in ['title']: if i in chapter and chapter[i] is not None and len(chapter[i]) > 0: titles.append(chapter[i]) tags.add( CHAP(element_id=chapterNr, start_time=floor( (chapterStartDT - broadcastStartDT).total_seconds() * 1000), end_time=ceil( (chapterEndDT - broadcastStartDT).total_seconds() * 1000), sub_frames=[ TIT2(text=[ " - ".join([" ".join(artists), " ".join(titles)]) ]) ])) chapterNr += 1 tocList = ",".join([str(i) for i in range(0, chapterNr)]) tags.add( CTOC(element_id="toc", flags=CTOCFlags.TOP_LEVEL | CTOCFlags.ORDERED, child_element_ids=[tocList], sub_frames=[TIT2(text=["Table Of Contents"])])) # ID3: cover image response = requests.get( broadcast['publicationOf']['defaultTeaserImage']['url'], timeout=5) if response.status_code == 200: tags.add( APIC(mime=response.headers['content-type'], desc="Front Cover", data=response.content)) # save ID3 tags tags.save(filename, v2_version=3)
has_tdrc = False for k, v in id3_tags.items(): if 'COMM' in k: has_comm = True comm = v if 'TDRC' in k: has_tdrc = True tdrc = v # for # conv comment encoding if has_comm: comm_list = comm.text comm_text = comm_list[0] comm_decode = comm_text.encode('latin1').decode('cp932') print('comm converted: ', comm_decode) id3_tags[COMM] = COMM(encoding=3, text = comm_decode) # if # conv TRDC to TYER for v2.3.0 if has_tdrc: tdrc_list = tdrc.text tdrc_text = tdrc_list[0] id3_tags.add( TYER(encoding=3, text = str(tdrc_text)) ) # if id3_tags.save(v2_version=3) print( 'converted: %s ' % filepath_utf16)
def CopyTagsToTranscodedFileMp3(losslessFile, lossyFile): # Because the input flac file is decoded to wav, all metadata is lost. We have to extract this metadata from # the flac file and put it directly into the generated mp3 file. from mutagen.flac import FLAC from mutagen.id3 import ID3 flacFile = FLAC(losslessFile) flacFileTags = flacFile.tags mp3File = ID3(lossyFile) mp3File.delete() for key,value in flacFileTags.items(): if key == 'title': from mutagen.id3 import TIT2 mp3File.add(TIT2(encoding=3, text=value)) elif key == 'album': from mutagen.id3 import TALB mp3File.add(TALB(encoding=3, text=value)) elif key == 'artist': from mutagen.id3 import TPE1 mp3File.add(TPE1(encoding=3, text=value)) elif key == 'tracknumber': from mutagen.id3 import TRCK mp3File.add(TRCK(encoding=3, text=value)) elif key == 'date': from mutagen.id3 import TDRC mp3File.add(TDRC(encoding=3, text=value)) elif key == 'genre': from mutagen.id3 import TCON mp3File.add(TCON(encoding=3, text=value)) elif key == 'discnumber': from mutagen.id3 import TPOS mp3File.add(TPOS(encoding=3, text=value)) elif key == 'composer': from mutagen.id3 import TCOM mp3File.add(TCOM(encoding=3, text=value)) elif key == 'conductor': from mutagen.id3 import TPE3 mp3File.add(TPE3(encoding=3, text=value)) elif key == 'ensemble': from mutagen.id3 import TPE2 mp3File.add(TPE2(encoding=3, text=value)) elif key == 'comment': from mutagen.id3 import COMM mp3File.add(COMM(encoding=3, text=value)) elif key == 'publisher': from mutagen.id3 import TPUB mp3File.add(TPUB(encoding=3, text=value)) elif key == 'opus': from mutagen.id3 import TIT3 mp3File.add(TIT3(encoding=3, text=value)) elif key == 'sourcemedia': from mutagen.id3 import TMED mp3File.add(TMED(encoding=3, text=value)) elif key == 'isrc': from mutagen.id3 import TSRC mp3File.add(TSRC(encoding=3, text=value)) elif key == 'license': from mutagen.id3 import TOWN mp3File.add(TOWN(encoding=3, text=value)) elif key == 'copyright': from mutagen.id3 import WCOP mp3File.add(WCOP(encoding=3, text=value)) elif key == 'encoded-by': from mutagen.id3 import TENC mp3File.add(TENC(encoding=3, text=value)) elif (key == 'part' or key == 'partnumber'): from mutagen.id3 import TIT3 mp3File.add(TIT3(encoding=3, text=value)) elif (key == 'lyricist' or key == 'textwriter'): from mutagen.id3 import TIT3 mp3File.add(TIT3(encoding=3, text=value)) else: from mutagen.id3 import TXXX mp3File.add(TXXX(encoding=3, text=value, desc=key)) mp3File.update_to_v24() mp3File.save() return
list = (json_obj['album'][0]) comment = list['strDescriptionEN'] except: try: comment = wikipedia.summary(query, sentences=4) comment = comment + "Wikipedia" except wikipedia.exceptions.PageError: print(colored('NO', 'red') + ',Album Description. Please Check') comment = "Please Add Information" for name in os.listdir(paths): if name.endswith(filetypes): filename = os.path.join(paths, name) if filename.endswith('.mp3'): #Write Comment To MP3 audio = ID3(filename) audio.add(COMM(encoding=3, text=comment)) audio.save() Easy3 = EMP3(filename) tag = TinyTag.get(filename) albumid = Easy3["musicbrainz_releasegroupid"][0] mbztrackids = Easy3["musicbrainz_trackid"][0] title = Easy3["title"][0] tpos = tag.track duration = tag.duration duration = math.ceil(duration) duration = time.strftime("%M:%S", time.gmtime(duration)) mbztrackid.append(mbztrackids) tracknames.append(title) trackpositions.append(tpos) trackdurations.append(duration) elif filename.endswith('.flac'):
# write ID3 Tag for partNo in range(len(showInfo['parts'])): # set ID3 tags try: tags = ID3(showInfo['parts'][partNo]['filepath'] + ".part") tags.delete() except ID3NoHeaderError: tags = ID3() tags.add(TRSN(text=[stationInfo['name']])) tags.add(TPE1(text=[stationInfo['name']])) tags.add(TALB(text=[showInfo['name']])) tags.add( TRCK(text=[str(partNo + 1) + "/" + str(len(showInfo['parts']))])) tags.add(TIT2(text=[showInfo['parts'][partNo]['title']])) tags.add(COMM(lang="deu", desc="desc", text=[showInfo['description']])) tags.add(TYER(text=[showInfo['start_dt'].strftime("%Y")])) tags.add(TDAT(text=[showInfo['start_dt'].strftime("%d%m")])) tags.add(TIME(text=[showInfo['start_dt'].strftime("%H%M")])) tags.add(TLEN(text=[showInfo['parts'][partNo]['duration_ms']])) tags.add(WOAS(url=showInfo['website'])) tags.add(WORS(url=stationInfo['website'])) for chapter in showInfo['parts'][partNo]['chapters']: tags.add( CHAP(element_id=chapter["id"], start_time=chapter["start_ms"], end_time=chapter["end_ms"], sub_frames=[TIT2(text=[chapter["title"]])])) tocList = ",".join([
lang=u'eng', desc=u'desc', text=lyrics)) print 'Added USLT frame to', fn # set title from filename; adjust to your needs if SET_OTHER_ID3_TAGS: title = unicode(os.path.splitext(os.path.split(fn)[-1])[0]) print title, print fname # title tags["TIT2"] = TIT2(encoding=3, text=title) tags["TALB"] = TALB(encoding=3, text=u'mutagen Album Name') tags["TPE2"] = TPE2(encoding=3, text=u'mutagen Band') tags["COMM"] = COMM(encoding=3, lang=u'eng', desc='desc', text=u'mutagen comment') # artist tags["TPE1"] = TPE1(encoding=3, text=u'mutagen Artist') # composer tags["TCOM"] = TCOM(encoding=3, text=u'mutagen Composer') # genre tags["TCON"] = TCON(encoding=3, text=u'mutagen Genre') tags["TDRC"] = TDRC(encoding=3, text=u'2010') #use to set track number #tags["TRCK"] = COMM(encoding=3, text=track_number) tags.save(fname) print 'Done'
def MP3Tagger(self): Caminho = self.lineEdit.text() if Caminho == "": QMessageBox.about(self, "ERRO", "Nenhum caminho especificado.") else: File = glob.glob(Caminho + "/*.mp3") for i in File: # Caminho completo do arquivo com extensão self.label_10.setText(_translate("MainWindow", "")) QtWidgets.QApplication.processEvents() Path = os.path.dirname(i) # Caninho completo da pasta do arquivo Name1 = os.path.basename(i) # Nome do arquivo completo com extensão Name2 = os.path.splitext(Name1) # Nome do arquivo dividido na extensão Name3 = Name2[0].split(" - ") # Nome do arquivo divido no artista e musica Name4 = ''.join(i for i in Name3[0] if not i.isdigit()) # Nome da música sem os números print("Caminho: " + i) print("Name1: " + str(Name1)) print("Name2: " + str(Name2)) print("Name3: " + str(Name3)) print("Name4: " + str(Name4)) self.label_10.setText(_translate("MainWindow", "Renomeando arquivo...")) QtWidgets.QApplication.processEvents() os.rename(i, Path + "\\" + Name3[1] + " -" + Name4 + ".mp3") # Renomeia o arquivo self.label_10.setText(_translate("MainWindow", "Buscando metadados no banco de dados do Spotify...")) QtWidgets.QApplication.processEvents() meta_tags = spotify_tools.generate_metadata(Name3[1] + " -" + Name4) # Gera as tags do mp3 if meta_tags == None: continue else: self.label_6.setText(_translate("MainWindow", str(meta_tags['artists'][0]['name']))) self.label_7.setText(_translate("MainWindow", str(meta_tags['name']))) self.label_8.setText(_translate("MainWindow", str(meta_tags['album']['name']))) self.label_10.setText(_translate("MainWindow", "Aplicando tags...")) ScriptFolder = os.path.dirname(os.path.realpath(sys.argv[0])) IMG = open(ScriptFolder + "\\" + 'cover2.jpg', 'wb') IMG.write(urllib.request.urlopen(meta_tags['album']['images'][0]['url']).read()) IMG.close() time.sleep(1) self.GenericCover3 = QPixmap('cover2.jpg') self.GenericCover4 = self.GenericCover3.scaled(141, 141) self.graphicsView.setPixmap(self.GenericCover4) QtWidgets.QApplication.processEvents() audiofile = MP3(Path + "\\" + Name3[1] + " -" + Name4 + ".mp3") audiofile.tags = None # Exclui qualquer tag antes de aplicar as novas (previne erro) audiofile.add_tags(ID3=EasyID3) audiofile['artist'] = meta_tags['artists'][0]['name'] audiofile['albumartist'] = meta_tags['artists'][0]['name'] audiofile['album'] = meta_tags['album']['name'] audiofile['title'] = meta_tags['name'] audiofile['tracknumber'] = [meta_tags['track_number'], meta_tags['total_tracks']] audiofile['discnumber'] = [meta_tags['disc_number'], 0] audiofile['date'] = meta_tags['release_date'] audiofile['originaldate'] = meta_tags['release_date'] audiofile['media'] = meta_tags['type'] audiofile['author'] = meta_tags['artists'][0]['name'] audiofile['lyricist'] = meta_tags['artists'][0]['name'] audiofile['arranger'] = meta_tags['artists'][0]['name'] audiofile['performer'] = meta_tags['artists'][0]['name'] audiofile['website'] = meta_tags['external_urls']['spotify'] audiofile['length'] = str(meta_tags['duration_ms'] / 1000.0) if meta_tags['publisher']: audiofile['encodedby'] = meta_tags['publisher'] if meta_tags['genre']: audiofile['genre'] = meta_tags['genre'] if meta_tags['copyright']: audiofile['copyright'] = meta_tags['copyright'] if meta_tags['external_ids']['isrc']: audiofile['isrc'] = meta_tags['external_ids']['isrc'] audiofile.save(v2_version=3) # For supported id3 tags: # https://github.com/quodlibet/mutagen/blob/master/mutagen/id3/_frames.py # Each class represents an id3 tag audiofile = ID3(Path + "\\" + Name3[1] + " -" + Name4 + ".mp3") year, *_ = meta_tags['release_date'].split('-') audiofile['TORY'] = TORY(encoding=3, text=year) audiofile['TYER'] = TYER(encoding=3, text=year) audiofile['TPUB'] = TPUB(encoding=3, text=meta_tags['publisher']) audiofile['COMM'] = COMM(encoding=3, text=meta_tags['external_urls']['spotify']) if meta_tags['lyrics']: audiofile['USLT'] = USLT(encoding=3, desc=u'Lyrics', text=meta_tags['lyrics']) try: albumart = urllib.request.urlopen(meta_tags['album']['images'][0]['url']) audiofile['APIC'] = APIC(encoding=3, mime='image/jpeg', type=3, desc=u'Cover', data=albumart.read()) albumart.close() except IndexError: pass audiofile.save(v2_version=3) self.label_10.setText(_translate("MainWindow", "Concluído.")) QtWidgets.QApplication.processEvents() time.sleep(2) # pausa dramática # Some com os textos: self.label_10.setText(_translate("MainWindow", "")) QtWidgets.QApplication.processEvents() QMessageBox.about(self, "Concluído", "Operação concluída.")
def comments(self, comments: str): self.file['COMM'] = COMM(encoding=3, text=comments) return self
#!/usr/bin/python3 from mutagen.id3 import ID3NoHeaderError from mutagen.id3 import ID3, COMM filename = input("Path to file: ") aid = input("Artist ID: ") try: tags = ID3(filename) except ID3NoHeaderError: tags = ID3() tags["COMM"] = COMM(encoding=3, lang=u'eng', desc='desc', text="a" + str(aid)) # TODO: Insert function to check sanity of user against database tags.save(filename)
def run(): parser = argparse.ArgumentParser( prog='odmpy', description='Download/return an Overdrive loan audiobook', epilog= 'Version {version}. [Python {py_major}.{py_minor}.{py_micro}-{platform}] ' 'Source at https://github.com/ping/odmpy/'.format( version=__version__, py_major=sys.version_info.major, py_minor=sys.version_info.minor, py_micro=sys.version_info.micro, platform=sys.platform, )) parser.add_argument('-v', '--verbose', dest='verbose', action='store_true', help='Enable more verbose messages for debugging') parser.add_argument( '-t', '--timeout', dest='timeout', type=int, default=10, help='Timeout (seconds) for network requests. Default 10.') subparsers = parser.add_subparsers( title='Available commands', dest='subparser_name', help='To get more help, use the -h option with the command.') parser_info = subparsers.add_parser( 'info', description='Get information about a loan file.', help='Get information about a loan file') parser_info.add_argument('odm_file', type=str, help='ODM file path') parser_dl = subparsers.add_parser('dl', description='Download from a loan file.', help='Download from a loan file') parser_dl.add_argument('-d', '--downloaddir', dest='download_dir', default='.', help='Download folder path') parser_dl.add_argument('-c', '--chapters', dest='add_chapters', action='store_true', help='Add chapter marks (experimental)') parser_dl.add_argument( '-m', '--merge', dest='merge_output', action='store_true', help='Merge into 1 file (experimental, requires ffmpeg)') parser_dl.add_argument( '--mergeformat', dest='merge_format', choices=['mp3', 'm4b'], default='mp3', help='Merged file format (m4b is slow, experimental, requires ffmpeg)') parser_dl.add_argument( '-k', '--keepcover', dest='always_keep_cover', action='store_true', help='Always generate the cover image file (cover.jpg)') parser_dl.add_argument('-f', '--keepmp3', dest='keep_mp3', action='store_true', help='Keep downloaded mp3 files (after merging)') parser_dl.add_argument('-j', '--writejson', dest='write_json', action='store_true', help='Generate a meta json file (for debugging)') parser_dl.add_argument('odm_file', type=str, help='ODM file path') parser_ret = subparsers.add_parser('ret', description='Return a loan file.', help='Return a loan file.') parser_ret.add_argument('odm_file', type=str, help='ODM file path') args = parser.parse_args() if args.verbose: logger.setLevel(logging.DEBUG) try: # test for odm file args.odm_file except AttributeError: parser.print_help() exit(0) xml_doc = xml.etree.ElementTree.parse(args.odm_file) root = xml_doc.getroot() # Return Book if args.subparser_name == 'ret': logger.info('Returning {} ...'.format(args.odm_file)) early_return_url = root.find('EarlyReturnURL').text try: early_return_res = requests.get(early_return_url, headers={'User-Agent': UA_LONG}, timeout=10) early_return_res.raise_for_status() logger.info('Loan returned successfully: {}'.format(args.odm_file)) except HTTPError as he: if he.response.status_code == 403: logger.warning('Loan is probably already returned.') sys.exit() logger.error( 'Unexpected HTTPError while trying to return loan {}'.format( args.odm_file)) logger.error('HTTPError: {}'.format(str(he))) logger.debug(he.response.content) sys.exit(1) except ConnectionError as ce: logger.error('ConnectionError: {}'.format(str(ce))) sys.exit(1) sys.exit() metadata = None for t in root.itertext(): if not t.startswith('<Metadata>'): continue metadata = xml.etree.ElementTree.fromstring( # remove invalid & char re.sub(r'\s&\s', ' & ', t)) break debug_meta = {} title = metadata.find('Title').text cover_url = metadata.find( 'CoverUrl').text if metadata.find('CoverUrl') != None else '' authors = [ unescape_html(c.text) for c in metadata.find('Creators') if 'Author' in c.attrib.get('role', '') ] if not authors: authors = [ unescape_html(c.text) for c in metadata.find('Creators') if 'Editor' in c.attrib.get('role', '') ] if not authors: authors = [unescape_html(c.text) for c in metadata.find('Creators')] publisher = metadata.find('Publisher').text description = metadata.find('Description').text if metadata.find( 'Description') is not None else '' debug_meta['meta'] = { 'title': title, 'coverUrl': cover_url, 'authors': authors, 'publisher': publisher, 'description': description, } # View Book Info if args.subparser_name == 'info': logger.info(u'{:10} {}'.format('Title:', colored.blue(title))) logger.info(u'{:10} {}'.format( 'Creators:', colored.blue(u', '.join([ u'{} ({})'.format(c.text, c.attrib['role']) for c in metadata.find('Creators') ])))) logger.info(u'{:10} {}'.format('Publisher:', metadata.find('Publisher').text)) logger.info(u'{:10} {}'.format( 'Subjects:', u', '.join([c.text for c in metadata.find('Subjects')]))) logger.info(u'{:10} {}'.format( 'Languages:', u', '.join([c.text for c in metadata.find('Languages')]))) logger.info(u'{:10} \n{}'.format('Description:', metadata.find('Description').text)) for formats in root.findall('Formats'): for f in formats: logger.info(u'\n{:10} {}'.format('Format:', f.attrib['name'])) parts = f.find('Parts') for p in parts: logger.info('* {} - {} ({:,.0f}kB)'.format( p.attrib['name'], p.attrib['duration'], math.ceil(1.0 * int(p.attrib['filesize']) / 1024))) sys.exit() # Download Book download_baseurl = '' download_parts = [] for formats in root.findall('Formats'): for f in formats: protocols = f.find('Protocols') for p in protocols: if p.attrib.get('method', '') != 'download': continue download_baseurl = p.attrib['baseurl'] break parts = f.find('Parts') for p in parts: download_parts.append(p.attrib) debug_meta['download_parts'] = download_parts logger.info('Downloading "{}" by "{}" in {} parts...'.format( colored.blue(title, bold=True), colored.blue(', '.join(authors)), len(download_parts))) # declare book folder/file names here together so we can catch problems from too long names book_folder = os.path.join( args.download_dir, u'{} - {}'.format(title.replace(os.sep, '-'), u', '.join(authors).replace(os.sep, '-'))) # for merged mp3 book_filename = os.path.join( book_folder, u'{} - {}.mp3'.format(title.replace(os.sep, '-'), u', '.join(authors).replace(os.sep, '-'))) # for merged m4b book_m4b_filename = os.path.join( book_folder, u'{} - {}.m4b'.format(title.replace(os.sep, '-'), u', '.join(authors).replace(os.sep, '-'))) if not os.path.exists(book_folder): try: os.makedirs(book_folder) except OSError as exc: if exc.errno not in ( 36, 63): # ref http://www.ioplex.com/~miallen/errcmpp.html raise # Ref OSError: [Errno 36] File name too long https://github.com/ping/odmpy/issues/5 # create book folder, file with just the title book_folder = os.path.join( args.download_dir, u'{}'.format(title.replace(os.sep, '-'))) os.makedirs(book_folder) book_filename = os.path.join( book_folder, u'{}.mp3'.format(title.replace(os.sep, '-'))) book_m4b_filename = os.path.join( book_folder, u'{}.m4b'.format(title.replace(os.sep, '-'))) cover_filename = os.path.join(book_folder, 'cover.jpg') debug_filename = os.path.join(book_folder, 'debug.json') if not os.path.isfile(cover_filename) and cover_url: cover_res = requests.get(cover_url, headers={'User-Agent': UA}) cover_res.raise_for_status() with open(cover_filename, 'wb') as outfile: outfile.write(cover_res.content) acquisition_url = root.find('License').find('AcquisitionUrl').text media_id = root.attrib['id'] client_id = str(uuid.uuid1()).upper() raw_hash = '{client_id}|{omc}|{os}|ELOSNOC*AIDEM*EVIRDREVO'.format( client_id=client_id, omc=OMC, os=OS) m = hashlib.sha1(raw_hash.encode('utf-16-le')) license_hash = base64.b64encode(m.digest()) # Extract license # License file is downloadable only once per odm # so we keep it in case downloads fail _, odm_filename = os.path.split(args.odm_file) license_file = os.path.join(args.download_dir, odm_filename.replace('.odm', '.license')) if os.path.isfile(license_file): logger.warning( 'Already downloaded license file: {}'.format(license_file)) else: # download license file params = OrderedDict([('MediaID', media_id), ('ClientID', client_id), ('OMC', OMC), ('OS', OS), ('Hash', license_hash)]) license_res = requests.get(acquisition_url, params=params, headers={'User-Agent': UA}, timeout=args.timeout, stream=True) try: license_res.raise_for_status() with open(license_file, 'wb') as outfile: for chunk in license_res.iter_content(1024): outfile.write(chunk) logger.debug('Saved license file {}'.format(license_file)) except HTTPError as he: if he.response.status_code == 404: # odm file has expired logger.error('The loan file "{}" has expired.' 'Please download again.'.format(args.odm_file)) else: logger.error(he.response.content) sys.exit(1) except ConnectionError as ce: logger.error('ConnectionError: {}'.format(str(ce))) sys.exit(1) license_xml_doc = xml.etree.ElementTree.parse(license_file) license_root = license_xml_doc.getroot() ns = '{http://license.overdrive.com/2008/03/License.xsd}' license_client = license_root.find('{}SignedInfo'.format(ns)).find( '{}ClientID'.format(ns)) license_client_id = license_client.text lic_file_contents = '' with open(license_file, 'r') as lic_file: lic_file_contents = lic_file.read() cover_bytes = None if os.path.isfile(cover_filename): with open(cover_filename, 'rb') as f: cover_bytes = f.read() track_count = 0 file_tracks = [] keep_cover = args.always_keep_cover audio_lengths_ms = [] for p in download_parts: part_number = int(p['number']) part_filename = os.path.join( book_folder, u'{}.mp3'.format( slugify(u'{} - Part {:02d}'.format(title, part_number), allow_unicode=True))) part_tmp_filename = u'{}.part'.format(part_filename) part_file_size = int(p['filesize']) part_url_filename = p['filename'] part_download_url = '{}/{}'.format(download_baseurl, part_url_filename) part_markers = [] if os.path.isfile(part_filename): logger.warning('Already saved {}'.format( colored.magenta(part_filename))) else: try: part_download_res = requests.get(part_download_url, headers={ 'User-Agent': UA, 'ClientID': license_client_id, 'License': lic_file_contents }, timeout=args.timeout, stream=True) part_download_res.raise_for_status() chunk_size = 1024 * 1024 expected_chunk_count = math.ceil(1.0 * part_file_size / chunk_size) with open(part_tmp_filename, 'wb') as outfile: for chunk in progress.bar( part_download_res.iter_content( chunk_size=chunk_size), label='Part {}'.format(part_number), expected_size=expected_chunk_count): if chunk: outfile.write(chunk) # try to remux file to remove mp3 lame tag errors cmd = [ 'ffmpeg', '-y', '-nostdin', '-hide_banner', '-loglevel', 'info' if logger.level == logging.DEBUG else 'error', '-i', part_tmp_filename, '-c:a', 'copy', '-c:v', 'copy', part_filename ] try: exit_code = subprocess.call(cmd) if exit_code: logger.warning( 'ffmpeg exited with the code: {0!s}'.format( exit_code)) logger.warning('Command: {0!s}'.format(' '.join(cmd))) os.rename(part_tmp_filename, part_filename) else: os.remove(part_tmp_filename) except Exception as ffmpeg_ex: logger.warning('Error executing ffmpeg: {}'.format( str(ffmpeg_ex))) os.rename(part_tmp_filename, part_filename) except HTTPError as he: logger.error('HTTPError: {}'.format(str(he))) logger.debug(he.response.content) sys.exit(1) except ConnectionError as ce: logger.error('ConnectionError: {}'.format(str(ce))) sys.exit(1) try: # Fill id3 info for mp3 part mutagen_audio = MP3(part_filename, ID3=ID3) if not mutagen_audio.tags: mutagen_audio.tags = ID3() if 'TIT2' not in mutagen_audio.tags: mutagen_audio.tags.add( TIT2(encoding=3, text=u'{}'.format(title))) if 'TIT3' not in mutagen_audio.tags: mutagen_audio.tags.add( TIT3(encoding=3, text=u'{}'.format(description))) if 'TALB' not in mutagen_audio.tags: mutagen_audio.tags.add( TALB(encoding=3, text=u'{}'.format(title))) if 'TPE1' not in mutagen_audio.tags: mutagen_audio.tags.add( TPE1(encoding=3, text=u'{}'.format(authors[0]))) if 'TPE2' not in mutagen_audio.tags: mutagen_audio.tags.add( TPE2(encoding=3, text=u'{}'.format(authors[0]))) if 'TRCK' not in mutagen_audio.tags: mutagen_audio.tags.add( TRCK(encoding=3, text=u'{}'.format(part_number))) if 'TPUB' not in mutagen_audio.tags: mutagen_audio.tags.add( TPUB(encoding=3, text=u'{}'.format(publisher))) if 'COMM' not in mutagen_audio.tags: mutagen_audio.tags.add( COMM(encoding=3, desc=u'Description', text=u'{}'.format(description))) if cover_bytes: mutagen_audio.tags.add( APIC(encoding=3, mime=u'image/jpeg', type=3, desc=u'Cover', data=cover_bytes)) mutagen_audio.save() audio_lengths_ms.append( int(round(mutagen_audio.info.length * 1000))) # Extract OD chapter info from mp3s for use in merged file if 'TXXX:OverDrive MediaMarkers' in mutagen_audio.tags \ and mutagen_audio.tags['TXXX:OverDrive MediaMarkers'].text: marker_text = mutagen_audio.tags[ 'TXXX:OverDrive MediaMarkers'].text[0] try: tree = xml.etree.ElementTree.fromstring(marker_text) except UnicodeEncodeError: tree = xml.etree.ElementTree.fromstring( marker_text.encode('ascii', 'ignore').decode('ascii')) for m in tree.iter('Marker'): marker_name = m.find('Name').text.strip() marker_timestamp = m.find('Time').text timestamp = None ts_mark = 0 # 2 timestamp formats found for r in ('%M:%S.%f', '%H:%M:%S.%f'): try: timestamp = time.strptime(marker_timestamp, r) ts = datetime.timedelta(hours=timestamp.tm_hour, minutes=timestamp.tm_min, seconds=timestamp.tm_sec) ts_mark = int(1000 * ts.total_seconds()) break except ValueError: pass if not timestamp: # some invalid timestamp string, e.g. 60:15.00 mobj = re.match(MARKER_TIMESTAMP_HHMMSS, marker_timestamp) if mobj: ts_mark = int(mobj.group('hr')) * 60 * 60 * 1000 + \ int(mobj.group('min')) * 60 * 1000 + \ int(mobj.group('sec')) * 1000 + \ int(mobj.group('ms')) else: mobj = re.match(MARKER_TIMESTAMP_MMSS, marker_timestamp) if mobj: ts_mark = int(mobj.group('min')) * 60 * 1000 + \ int(mobj.group('sec')) * 1000 + \ int(mobj.group('ms')) else: raise ValueError( 'Invalid marker timestamp: {}'.format( marker_timestamp)) track_count += 1 part_markers.append((u'ch{:02d}'.format(track_count), marker_name, ts_mark)) if args.add_chapters and not args.merge_output: # set the chapter marks generated_markers = [] for j, file_marker in enumerate(part_markers): generated_markers.append({ 'id': file_marker[0], 'text': file_marker[1], 'start_time': int(file_marker[2]), 'end_time': int( round(mutagen_audio.info.length * 1000) if j == (len(part_markers) - 1) else part_markers[j + 1][2]), }) mutagen_audio.tags.add( CTOC(element_id=u'toc', flags=CTOCFlags.TOP_LEVEL | CTOCFlags.ORDERED, child_element_ids=[ m['id'].encode('ascii') for m in generated_markers ], sub_frames=[TIT2(text=[u'Table of Contents'])])) for i, m in enumerate(generated_markers): mutagen_audio.tags.add( CHAP(element_id=m['id'].encode('ascii'), start_time=m['start_time'], end_time=m['end_time'], sub_frames=[TIT2(text=[u'{}'.format(m['text'])]) ])) start_time = datetime.timedelta( milliseconds=m['start_time']) end_time = datetime.timedelta(milliseconds=m['end_time']) logger.debug( u'Added chap tag => {}: {}-{} "{}" to "{}"'.format( colored.cyan(m['id']), start_time, end_time, colored.cyan(m['text']), colored.blue(part_filename))) if len(generated_markers) == 1: # Weird player problem on voice where title is shown instead of chapter title mutagen_audio.tags.add( TIT2(encoding=3, text=u'{}'.format(title))) mutagen_audio.save() except Exception as e: logger.warning('Error saving ID3: {}'.format( colored.red(str(e), bold=True))) keep_cover = True logger.info('Saved "{}"'.format(colored.magenta(part_filename))) file_tracks.append({ 'file': part_filename, 'markers': part_markers, }) # end loop: for p in download_parts: debug_meta['audio_lengths_ms'] = audio_lengths_ms debug_meta['file_tracks'] = file_tracks if args.merge_output: if os.path.isfile(book_filename if args.merge_format == 'mp3' else book_m4b_filename): logger.warning('Already saved "{}"'.format( colored.magenta(book_filename if args.merge_format == 'mp3' else book_m4b_filename))) sys.exit(0) logger.info('Generating "{}"...'.format( colored.magenta(book_filename if args.merge_format == 'mp3' else book_m4b_filename))) # We can't directly generate a m4b here even if specified because eyed3 doesn't support m4b/mp4 temp_book_filename = '{}.part'.format(book_filename) cmd = [ 'ffmpeg', '-y', '-nostdin', '-hide_banner', '-loglevel', 'info' if logger.level == logging.DEBUG else 'error', '-stats', '-i', 'concat:{}'.format('|'.join([ft['file'] for ft in file_tracks])), '-acodec', 'copy', '-b:a', '64k', # explicitly set audio bitrate '-f', 'mp3', temp_book_filename ] exit_code = subprocess.call(cmd) if exit_code: logger.error( 'ffmpeg exited with the code: {0!s}'.format(exit_code)) logger.error('Command: {0!s}'.format(' '.join(cmd))) exit(exit_code) os.rename(temp_book_filename, book_filename) mutagen_audio = MP3(book_filename, ID3=ID3) if not mutagen_audio.tags: mutagen_audio.tags = ID3() # Overwrite title since it gets picked up from the first track by default mutagen_audio.tags.add(TIT2(encoding=3, text=u'{}'.format(title))) if 'TALB' not in mutagen_audio.tags: mutagen_audio.tags.add(TALB(encoding=3, text=u'{}'.format(title))) if 'TPE1' not in mutagen_audio.tags: mutagen_audio.tags.add( TPE1(encoding=3, text=u'{}'.format(authors[0]))) if 'TPE2' not in mutagen_audio.tags: mutagen_audio.tags.add( TPE2(encoding=3, text=u'{}'.format(authors[0]))) if 'TPUB' not in mutagen_audio.tags: mutagen_audio.tags.add( TPUB(encoding=3, text=u'{}'.format(publisher))) if 'COMM' not in mutagen_audio.tags: mutagen_audio.tags.add( COMM(encoding=3, desc=u'Description', text=u'{}'.format(description))) mutagen_audio.save() if args.add_chapters: merged_markers = [] for i, f in enumerate(file_tracks): prev_tracks_len_ms = 0 if i == 0 else reduce( lambda x, y: x + y, audio_lengths_ms[0:i]) this_track_endtime_ms = int( reduce(lambda x, y: x + y, audio_lengths_ms[0:i + 1])) file_markers = f['markers'] for j, file_marker in enumerate(file_markers): merged_markers.append({ 'id': file_marker[0], 'text': u'{}'.format(file_marker[1]), 'start_time': int(file_marker[2]) + prev_tracks_len_ms, 'end_time': int(this_track_endtime_ms if j == ( len(file_markers) - 1) else file_markers[j + 1][2] + prev_tracks_len_ms), }) debug_meta['merged_markers'] = merged_markers mutagen_audio.tags.add( CTOC(element_id=u'toc', flags=CTOCFlags.TOP_LEVEL | CTOCFlags.ORDERED, child_element_ids=[ m['id'].encode('ascii') for m in merged_markers ], sub_frames=[TIT2(text=[u"Table of Contents"])])) for i, m in enumerate(merged_markers): mutagen_audio.tags.add( CHAP(element_id=m['id'].encode('ascii'), start_time=m['start_time'], end_time=m['end_time'], sub_frames=[TIT2(text=[u'{}'.format(m['text'])])])) start_time = datetime.timedelta(milliseconds=m['start_time']) end_time = datetime.timedelta(milliseconds=m['end_time']) logger.debug( u'Added chap tag => {}: {}-{} "{}" to "{}"'.format( colored.cyan(m['id']), start_time, end_time, colored.cyan(m['text']), colored.blue(book_filename))) mutagen_audio.save() if args.merge_format == 'mp3': logger.info('Merged files into "{}"'.format( colored.magenta(book_filename if args.merge_format == 'mp3' else book_m4b_filename))) if args.merge_format == 'm4b': temp_book_m4b_filename = '{}.part'.format(book_m4b_filename) cmd = [ 'ffmpeg', '-y', '-nostdin', '-hide_banner', '-loglevel', 'info' if logger.level == logging.DEBUG else 'error', '-stats', '-i', book_filename, ] if os.path.isfile(cover_filename): cmd.extend(['-i', cover_filename]) cmd.extend([ '-map', '0:a', '-c:a', 'aac', '-b:a', '64k', # explicitly set audio bitrate ]) if os.path.isfile(cover_filename): cmd.extend([ '-map', '1:v', '-c:v', 'copy', '-disposition:v:0', 'attached_pic', ]) cmd.extend(['-f', 'mp4', temp_book_m4b_filename]) exit_code = subprocess.call(cmd) if exit_code: logger.error( 'ffmpeg exited with the code: {0!s}'.format(exit_code)) logger.error('Command: {0!s}'.format(' '.join(cmd))) exit(exit_code) os.rename(temp_book_m4b_filename, book_m4b_filename) logger.info('Merged files into "{}"'.format( colored.magenta(book_m4b_filename))) try: os.remove(book_filename) except Exception as e: logger.warning('Error deleting "{}": {}'.format( book_filename, str(e))) if not args.keep_mp3: for f in file_tracks: try: os.remove(f['file']) except Exception as e: logger.warning('Error deleting "{}": {}'.format( f['file'], str(e))) if not keep_cover: try: os.remove(cover_filename) except Exception as e: logger.warning('Error deleting "{}": {}'.format( cover_filename, str(e))) if args.write_json: with open(debug_filename, 'w') as outfile: json.dump(debug_meta, outfile, indent=2)
def get_song(show, download, name): begin, end = [int(value) for value in get_num(download)] url_base = "http://zaycev.net" url_add = '' ua = UserAgent() header = {'User-Agent': ua.random} param = None if name: url_add = "search.html" query = '+'.join(name) param = {"query_search": query} http = get(f'{url_base}/{url_add}', headers=header, params=param) response = html.fromstring(http.text) links = response.xpath('//div[@data-rbt-content-id]/@data-url') artists = response.xpath('//*[@itemprop="byArtist"]/a/text()') songs = response.xpath('//*[@itemprop="name"]/a/text()') begin = max(begin, 1) if end == -1 or end > len(songs): end = len(songs) begin = min(begin, end) if show: print('Доступные композиции:') if links: i = 1 shift = 0 while True: url = get(f'{url_base}{links[i - 1]}').json()['url'] presence = head(url) if presence.status_code != 200 and presence.headers.get( 'Content-Type') != 'audio/mpeg': i += 1 continue shift += 1 if shift >= begin: title = f'{artists[i-1].strip()} – {songs[i-1].strip()}.mp3' size = round( int(presence.headers.get('Content-Length', '0')) / 1048576, 1) if show: print(f'{shift}. {title} ({size} Мб)') else: title = title.replace(':', ' ').replace('\\', ' ').replace( '*', ' ').replace(' ', ' ') while exists(title): title = '_' + title number = '' if begin == end else f"{shift}." print(f"Загружается: {number}{title}", end='', flush=True) song = get(url, stream=True) save_song_to_file(title, song, number) try: audio = ID3(title) except ID3NoHeaderError: pass else: song_name = audio['TIT2'][0][:-13] audio.add(TIT2(text=song_name)) audio.add(TALB(text='')) audio.add(COMM(lang='eng', text='')) audio.save() print(f"\rЗагружено: {number}{title} ") if shift >= end or i >= len(links): break i += 1
def save_mp3_metadata(self, file_path, data): """ Saves the given metadata for an MP3 file. Metadata.save_mp3_metadata(str, dict) """ audio = ID3(file_path) # Writing MP3 tags needs ID3 mp3_audio, format = self.get_mutagen_parser(file_path) if data.get("title"): audio.add(TIT2(encoding=3, text=unicode(data['title']))) if data.get("artist"): audio.add(TPE1(encoding=3, text=unicode(data['artist']))) if data.get("album"): audio.add(TALB(encoding=3, text=unicode(data['album']))) if data.get("genre"): audio.add(TCON(encoding=3, text=unicode(data['genre']))) if data.get("year"): audio.add(TDRC(encoding=3, text=unicode(data['year']))) if data.get("album_artist"): audio.add(TPE2(encoding=3, text=unicode(data['album_artist']))) if data.get("track_number", False) and data.get("total_tracks", False): audio.add( TRCK(encoding=3, text=unicode(data['track_number'] + '/' + data['total_tracks']))) elif data.get("track_number"): total_tracks = self.get_total_tracks(mp3_audio, format) if total_tracks == None: audio.add(TRCK(encoding=3, text=unicode(data['track_number']))) else: audio.add( TRCK(encoding=3, text=unicode(data['track_number'] + '/' + total_tracks))) elif data.get("total_tracks"): t_no = self.get_track_number(mp3_audio, format) if t_no == None: audio.add( TRCK(encoding=3, text=unicode(" /" + data["total_tracks"]))) else: audio.add( TRCK(encoding=3, text=unicode(t_no + '/' + data["total_tracks"]))) if data.get("disc_number", False) and data.get("total_discs", False): audio.add( TPOS(encoding=3, text=unicode(data['disc_number'] + '/' + data['total_discs']))) elif data.get("disc_number"): total_discs = self.get_total_discs(mp3_audio, format) if total_discs == None: audio.add(TPOS(encoding=3, text=unicode(data['disc_number']))) else: audio.add( TPOS(encoding=3, text=unicode(data['disc_number'] + '/' + total_discs))) elif data.get("total_discs"): d_no = self.get_disc_number(mp3_audio, format) if d_no == None: audio.add( TPOS(encoding=3, text=unicode(" /" + data["total_discs"]))) else: audio.add( TPOS(encoding=3, text=unicode(d_no + '/' + data["total_discs"]))) if data.get("composer"): audio.add(TCOM(encoding=3, text=unicode(data['composer']))) if data.get("publisher"): audio.add(TPUB(encoding=3, text=unicode(data['publisher']))) if data.get("comments"): audio.add( COMM(encoding=3, lang="eng", desc="", text=unicode(data['comments']))) audio.save()
fname = os.path.splitext(basename)[0] filepath_change = fname + "_change.mp3" shutil.copyfile(filepath, filepath_change) # find comment tag id3_tags = ID3(filepath_change) # find comment tag has_comm = False for k, v in id3_tags.items(): if "COMM" in k: has_comm = True comm = v # for if not has_comm: print('no comment') exit() # if comm_text_list = comm.text comm_text = = comm_text_list[0] # change comment id3_tags[COMM] = COMM(encoding=3, text='new comment') id3_tags.save() print( 'changed: %s ' % filepath_change)
def as_mp3(self): """ Embed metadata to MP3 files. """ music_file = self.music_file meta_tags = self.meta_tags # EasyID3 is fun to use ;) # For supported easyid3 tags: # https://github.com/quodlibet/mutagen/blob/master/mutagen/easyid3.py # Check out somewhere at end of above linked file audiofile = EasyID3(music_file) audiofile['artist'] = meta_tags['artists'][0]['name'] audiofile['albumartist'] = meta_tags['artists'][0]['name'] audiofile['album'] = meta_tags['album']['name'] audiofile['title'] = meta_tags['name'] audiofile['tracknumber'] = [ meta_tags['track_number'], meta_tags['total_tracks'] ] audiofile['discnumber'] = [meta_tags['disc_number'], 0] audiofile['date'] = meta_tags['release_date'] audiofile['originaldate'] = meta_tags['release_date'] audiofile['media'] = meta_tags['type'] audiofile['author'] = meta_tags['artists'][0]['name'] audiofile['lyricist'] = meta_tags['artists'][0]['name'] audiofile['arranger'] = meta_tags['artists'][0]['name'] audiofile['performer'] = meta_tags['artists'][0]['name'] audiofile['website'] = meta_tags['external_urls']['spotify'] audiofile['length'] = str(meta_tags['duration']) if meta_tags['publisher']: audiofile['encodedby'] = meta_tags['publisher'] if meta_tags['genre']: audiofile['genre'] = meta_tags['genre'] if meta_tags['copyright']: audiofile['copyright'] = meta_tags['copyright'] if meta_tags['external_ids']['isrc']: audiofile['isrc'] = meta_tags['external_ids']['isrc'] audiofile.save(v2_version=3) # For supported id3 tags: # https://github.com/quodlibet/mutagen/blob/master/mutagen/id3/_frames.py # Each class represents an id3 tag audiofile = ID3(music_file) audiofile['TORY'] = TORY(encoding=3, text=meta_tags['year']) audiofile['TYER'] = TYER(encoding=3, text=meta_tags['year']) audiofile['TPUB'] = TPUB(encoding=3, text=meta_tags['publisher']) audiofile['COMM'] = COMM(encoding=3, text=meta_tags['external_urls']['spotify']) if meta_tags['lyrics']: audiofile['USLT'] = USLT(encoding=3, desc=u'Lyrics', text=meta_tags['lyrics']) try: albumart = urllib.request.urlopen( meta_tags['album']['images'][0]['url']) audiofile['APIC'] = APIC(encoding=3, mime='image/jpeg', type=3, desc=u'Cover', data=albumart.read()) albumart.close() except IndexError: pass audiofile.save(v2_version=3) return True
def as_mp3(self, path, metadata, cached_albumart=None): """ Apply metadata on MP3 media files. Parameters ---------- path: `str` Path to the media file. metadata: `dict` Metadata (standardized) to apply to the media file. cached_albumart: `bool` An albumart image binary. If passed, the albumart URL present in the ``metadata`` won't be downloaded or used. """ logger.debug('Writing MP3 metadata to "{path}".'.format(path=path)) # EasyID3 is fun to use ;) # For supported easyid3 tags: # https://github.com/quodlibet/mutagen/blob/master/mutagen/easyid3.py # Check out somewhere at end of above linked file audiofile = EasyID3(path) self._embed_basic_metadata(audiofile, metadata, "mp3", preset=TAG_PRESET) audiofile["media"] = metadata["type"] audiofile["author"] = metadata["artists"][0]["name"] audiofile["lyricist"] = metadata["artists"][0]["name"] audiofile["arranger"] = metadata["artists"][0]["name"] audiofile["performer"] = metadata["artists"][0]["name"] provider = metadata["provider"] audiofile["website"] = metadata["external_urls"][provider] audiofile["length"] = str(metadata["duration"]) if metadata["publisher"]: audiofile["encodedby"] = metadata["publisher"] if metadata["external_ids"]["isrc"]: audiofile["isrc"] = metadata["external_ids"]["isrc"] audiofile.save(v2_version=3) # For supported id3 tags: # https://github.com/quodlibet/mutagen/blob/master/mutagen/id3/_frames.py # Each class in the linked source file represents an id3 tag audiofile = ID3(path) if metadata["year"]: audiofile["TORY"] = TORY(encoding=3, text=metadata["year"]) audiofile["TYER"] = TYER(encoding=3, text=metadata["year"]) if metadata["publisher"]: audiofile["TPUB"] = TPUB(encoding=3, text=metadata["publisher"]) provider = metadata["provider"] audiofile["COMM"] = COMM(encoding=3, text=metadata["external_urls"][provider]) if metadata["lyrics"]: audiofile["USLT"] = USLT(encoding=3, desc=u"Lyrics", text=metadata["lyrics"]) if cached_albumart is None: cached_albumart = urllib.request.urlopen( metadata["album"]["images"][0]["url"]).read() try: audiofile["APIC"] = APIC( encoding=3, mime="image/jpeg", type=3, desc=u"Cover", data=cached_albumart, ) except IndexError: pass audiofile.save(v2_version=3)