def process(folder): os.chdir(folder) folder = os.getcwd() walk_result = os.walk(folder) for f in walk_result: (dirpath, dirnames, filenames) = f args = dirpath[len(folder):].split('/') artist = 'Unknown' if (len(args) < 2) else args[1] album = 'Unknown' if (len(args) < 3) else args[2] print "Artist:", artist, ", album:", album for file in filenames: if file[-4:].lower() != '.mp3': continue title = file[:-4] fullpath = dirpath + "/" + file print title if (os.access(fullpath, os.R_OK | os.W_OK) == False): print 'Error: bad permissions on', fullpath continue #mutagen.id3.delete(fullpath) id3 = mutagen.id3.ID3() id3.add(mutagen.id3.TPE1(encoding = 3, text = artist.decode("utf-8"))) id3.add(mutagen.id3.TALB(encoding = 3, text = album.decode("utf-8"))) id3.add(mutagen.id3.TIT2(encoding = 3, text = title.decode("utf-8"))) id3.save(fullpath)
def set(filename, src, cover=None): mode = ext(filename) if mode == "mp3": dst = mutagen.easyid3.EasyID3() copy_tags(src, dst) dst.save(filename) if cover: id3 = mutagen.mp3.MP3(filename) id3.tags.add( mutagen.id3.APIC(encoding=3, mime="image/jpeg", type=3, desc=u"Cover", data=open(cover).read()) ) id3.save() elif mode == "ogg": dst = mutagen.oggvorbis.OggVorbis(filename) copy_tags(src, dst) if cover: dst["COVERARTMIME"] = u"image/jpeg" dst["COVERARTTYPE"] = u"3" dst["COVERARTDESCRIPTION"] = u"Cover" dst["COVERART"] = unicode(base64.b64encode(open(cover).read())) dst.save() elif mode == "flac": dst = mutagen.flac.FLAC(filename) copy_tags(src, dst) dst.save() else: logger.info(filename + ": tags not supported")
def test_nofile_silencetag(self): id3 = ID3(self.filename) os.unlink(self.filename) id3.save(self.filename) with open(self.filename, 'rb') as h: self.assertEquals(b'ID3', h.read(3)) self.test_same()
def delete_frames(deletes, filenames): try: deletes = frame_from_fsnative(deletes) except ValueError as err: print_(text_type(err), file=sys.stderr) frames = deletes.split(",") for filename in filenames: with _sig.block(): if verbose: print_(u"deleting %s from" % deletes, filename, file=sys.stderr) try: id3 = mutagen.id3.ID3(filename) except mutagen.id3.ID3NoHeaderError: if verbose: print_(u"No ID3 header found; skipping.", file=sys.stderr) except Exception as err: print_(text_type(err), file=sys.stderr) raise SystemExit(1) else: for frame in frames: id3.delall(frame) id3.save()
def process(folder): os.chdir(folder) folder = os.getcwd() walk_result = os.walk(folder) for f in walk_result: (dirpath, dirnames, filenames) = f args = dirpath[len(folder) :].split("/") artist = "Unknown" if (len(args) < 2) else args[1] album = "Unknown" if (len(args) < 3) else args[2] print "Artist:", artist, ", album:", album for file in filenames: if file[-4:].lower() != ".mp3": continue title = file[:-4] fullpath = dirpath + "/" + file print title if os.access(fullpath, os.R_OK | os.W_OK) == False: print "Error: bad permissions on", fullpath continue # mutagen.id3.delete(fullpath) id3 = mutagen.id3.ID3() id3.add(mutagen.id3.TPE1(encoding=3, text=artist.decode("utf-8"))) id3.add(mutagen.id3.TALB(encoding=3, text=album.decode("utf-8"))) id3.add(mutagen.id3.TIT2(encoding=3, text=title.decode("utf-8"))) id3.save(fullpath)
def test_emptyfile_silencetag(self): id3 = ID3(self.filename) with open(self.filename, 'wb') as h: h.truncate() id3.save(self.filename) with open(self.filename, 'rb') as h: self.assertEquals(b'ID3', h.read(3)) self.test_same()
def test_same_v23(self): id3 = ID3(self.filename, v2_version=3) id3.save(v2_version=3) id3 = ID3(self.filename) self.assertEqual(id3.version, (2, 3, 0)) self.assertEquals(id3["TALB"], "Quod Libet Test Data") self.assertEquals(id3["TCON"], "Silence") self.assertEquals(id3["TIT2"], "Silence") self.assertEquals(id3["TPE1"], "piman/jzig")
def copy(src, dst, merge, write_v1=True, excluded_tags=None, verbose=False): """Returns 0 on success""" if excluded_tags is None: excluded_tags = [] try: id3 = mutagen.id3.ID3(src, translate=False) except mutagen.id3.ID3NoHeaderError: print_(u"No ID3 header found in ", src, file=sys.stderr) return 1 except Exception as err: print_(str(err), file=sys.stderr) return 1 if verbose: print_(u"File", src, u"contains:", file=sys.stderr) print_(id3.pprint(), file=sys.stderr) for tag in excluded_tags: id3.delall(tag) if merge: try: target = mutagen.id3.ID3(dst, translate=False) except mutagen.id3.ID3NoHeaderError: # no need to merge pass except Exception as err: print_(str(err), file=sys.stderr) return 1 else: for frame in id3.values(): target.add(frame) id3 = target # if the source is 2.3 save it as 2.3 if id3.version < (2, 4, 0): id3.update_to_v23() v2_version = 3 else: id3.update_to_v24() v2_version = 4 try: id3.save(dst, v1=(2 if write_v1 else 0), v2_version=v2_version) except Exception as err: print_(u"Error saving", dst, u":\n%s" % text_type(err), file=sys.stderr) return 1 else: if verbose: print_(u"Successfully saved", dst, file=sys.stderr) return 0
def test_empty_plustag_minustag_empty(self): id3 = ID3(self.filename) with open(self.filename, 'wb') as h: h.truncate() id3.save() id3.delete() self.failIf(id3) with open(self.filename, 'rb') as h: self.assertEquals(h.read(10), b'')
def update(options, filenames): encoding = options.encoding or getpreferredencoding() verbose = options.verbose noupdate = options.noupdate force_v1 = options.force_v1 remove_v1 = options.remove_v1 def conv(uni): return uni.encode('iso-8859-1').decode(encoding) for filename in filenames: with _sig.block(): if verbose != "quiet": print_(u"Updating", filename) if has_id3v1(filename) and not noupdate and force_v1: mutagen.id3.delete(filename, False, True) try: id3 = mutagen.id3.ID3(filename) except mutagen.id3.ID3NoHeaderError: if verbose != "quiet": print_(u"No ID3 header found; skipping...") continue except Exception as err: print_(text_type(err), file=sys.stderr) continue for tag in filter(lambda t: t.startswith(("T", "COMM")), id3): frame = id3[tag] if isinstance(frame, mutagen.id3.TimeStampTextFrame): # non-unicode fields continue try: text = frame.text except AttributeError: continue try: text = [conv(x) for x in frame.text] except (UnicodeError, LookupError): continue else: frame.text = text if not text or min(map(isascii, text)): frame.encoding = 3 else: frame.encoding = 1 if verbose == "debug": print_(id3.pprint()) if not noupdate: if remove_v1: id3.save(filename, v1=False) else: id3.save(filename)
def split_audio_file(self, file=""): file_root, file_ext = os.path.splitext(file) file_format = file_ext.replace(".", "").lower() if file_format not in ["mp3", "m4a", "mp4"]: raise FileNotSupportedError(file_format) file_size = os.path.getsize(file) if file_size > self.MAX_CONVERT_FILE_SIZE: raise FileTooLargeError(file_size) file_parts = [] if file_size <= self.MAX_TG_FILE_SIZE: if file_format == "mp3": file_parts.append(file) else: logger.info("Converting: %s", file) try: sound = AudioSegment.from_file(file, file_format) file_converted = file.replace(file_ext, ".mp3") sound.export(file_converted, format="mp3") del sound gc.collect() file_parts.append(file_converted) except (OSError, MemoryError) as exc: gc.collect() raise FileNotConvertedError else: logger.info("Splitting: %s", file) try: id3 = mutagen.id3.ID3(file, translate=False) except: id3 = None parts_number = file_size // self.MAX_TG_FILE_SIZE + 1 try: sound = AudioSegment.from_file(file, file_format) part_size = len(sound) / parts_number for i in range(parts_number): file_part = file.replace( file_ext, ".part{}{}".format(str(i + 1), file_ext)) part = sound[part_size * i:part_size * (i + 1)] part.export(file_part, format="mp3") del part gc.collect() if id3: try: id3.save(file_part, v1=2, v2_version=4) except: pass file_parts.append(file_part) # https://github.com/jiaaro/pydub/issues/135 # https://github.com/jiaaro/pydub/issues/89#issuecomment-75245610 del sound gc.collect() except (OSError, MemoryError) as exc: gc.collect() raise FileConvertedPartiallyError(file_parts) return file_parts
def update(options, filenames): encoding = options.encoding or locale.getpreferredencoding() verbose = options.verbose noupdate = options.noupdate force_v1 = options.force_v1 remove_v1 = options.remove_v1 def conv(uni): #return uni.encode('iso-8859-1').decode(encoding) return h2p(uni) for filename in filenames: if verbose != "quiet": print "Updating", filename if has_id3v1(filename) and not noupdate and force_v1: mutagen.id3.delete(filename, False, True) try: id3 = mutagen.id3.ID3(filename) except mutagen.id3.ID3NoHeaderError: if verbose != "quiet": print "No ID3 header found; skipping..." continue except Exception, err: if verbose != "quiet": print str(err) continue for tag in filter(lambda t: t.startswith("T"), id3): frame = id3[tag] if isinstance(frame, mutagen.id3.TimeStampTextFrame): # non-unicode fields continue try: text = frame.text except AttributeError: continue try: text = map(conv, frame.text) except (UnicodeError, LookupError): continue else: frame.text = text if not text or min(map(isascii, text)): frame.encoding = 3 else: frame.encoding = 1 enc = locale.getpreferredencoding() if verbose == "debug": print id3.pprint().encode(enc, "replace") if not noupdate: if remove_v1: id3.save(filename, v1=False) else: id3.save(filename)
def write_tags(self): """Write all ID3v2.4 tags by mapping dub2id3_dict dictionnary with the respect of mutagen classes and methods""" from mutagen import id3 id3 = id3.ID3(self.dest) for tag in self.metadata.keys(): if tag in self.dub2id3_dict.keys(): frame_text = self.dub2id3_dict[tag] value = self.metadata[tag] frame = mutagen.id3.Frames[frame_text](3,value) id3.add(frame) id3.save()
def test_merge_dst_no_tag(self): id3 = ID3(self.filename) id3.delete() id3.save(v2_version=3) with open(self.blank_file, "wb") as h: h.write(b"SOMEDATA") res, out, err = self.call2( self.filename, self.blank_file, fsn(u"--merge")) assert not any([res, out, err]) result = ID3(self.blank_file) assert result.version == (2, 3, 0)
def write_metadata(self): """Write all ID3v2.4 tags to file from self.metadata""" from mutagen import id3 id3 = id3.ID3(self.filename) for tag in self.metadata.keys(): value = self.metadata[tag] frame = mutagen.id3.Frames[tag](3,value) try: id3.add(frame) except: raise IOError('EncoderError: cannot tag "'+tag+'"') try: id3.save() except: raise IOError('EncoderError: cannot write tags')
def write_metadata(self): """Write all ID3v2.4 tags to file from self.metadata""" import mutagen from mutagen import id3 id3 = id3.ID3(self.filename) for tag in self.metadata.keys(): value = self.metadata[tag] frame = mutagen.id3.Frames[tag](3, value) try: id3.add(frame) except: raise IOError('EncoderError: cannot tag "' + tag + '"') try: id3.save() except: raise IOError('EncoderError: cannot write tags')
def test_unknown_chap(self): # add ctoc id3 = ID3(self.filename) id3.add(CTOC(element_id="foo", flags=3, child_element_ids=["ch0"], sub_frames=[TIT2(encoding=3, text=["bla"])])) id3.save() # pretend we don't know ctoc and save id3 = ID3(self.filename, known_frames={"CTOC": CTOC}) ctoc = id3.getall("CTOC")[0] self.assertFalse(ctoc.sub_frames) self.assertTrue(ctoc.sub_frames.unknown_frames) id3.save() # make sure we wrote all sub frames back id3 = ID3(self.filename) self.assertEqual( id3.getall("CTOC")[0].sub_frames.getall("TIT2")[0].text, ["bla"])
def ape_to_id3(files, version=None): """ Moves gain related tags from APEv2 to ID3v2, then deletes APEv2. :param files: list of files to act on :param version: ID3 tag version to save as (optional, tags will be ID3v2.4 if not provided) """ for file in files: id3 = _get_mutagen_id3(file) ape = _get_mutagen_apev2(file) updated = add_to_id3(read_from_apev2(ape), id3) if updated > 0: print 'writing id3' id3.save(file) ape.delete(file) if version != None: pymp3utils.set_id3_version(file, version)
def test_merge(self): id3 = ID3(self.filename) id3.delete() id3.add(mutagen.id3.TALB(text=[u"foo"])) id3.save(v2_version=3) target = ID3() target.add(mutagen.id3.TPE1(text=[u"bar", u"quux"])) target.save(self.blank_file, v2_version=4) res, out, err = self.call2( self.filename, self.blank_file, fsn(u"--merge")) assert not any([res, out, err]) result = ID3(self.blank_file) assert result.version == (2, 4, 0) assert result.getall("TALB")[0].text == [u"foo"] assert result.getall("TPE1")[0].text == [u"bar", u"quux"]
def write_tags(self): """Write all ID3v2.4 tags by mapping dub2id3_dict dictionnary with the respect of mutagen classes and methods""" from mutagen import id3 id3 = id3.ID3(self.dest) for tag in self.metadata.keys(): if tag in self.dub2id3_dict.keys(): frame_text = self.dub2id3_dict[tag] value = self.metadata[tag] frame = mutagen.id3.Frames[frame_text](3,value) try: id3.add(frame) except: raise IOError('EncoderError: cannot tag "'+tag+'"') try: id3.save() except: raise IOError('EncoderError: cannot write tags')
for n,v in optList: if n in ('--artist'): artist = v if n in ('--album'): album = v for filePath in filePaths: print '* '+filePath if not artist and not album: id3 = ID3(filePath) print id3 else: id3 = ID3(filePath) if album: id3.setAlbum(album) if artist: id3.setArtist(artist) id3.save() return 0 #------------------------------------------------------------------------------- if __name__ == '__main__' or __name__ == sys.argv[0]: sys.exit(test(sys.argv)) #------------------------------------------------------------------------------- # end
except mutagen.id3.ID3NoHeaderError: id3 = mutagen.id3.ID3() if ft == u'mp3': # adding new id3 frames id3.add(TIT2(encoding=3, text=disc.track_list[c].title)) id3.add(TPE1(encoding=3, text=disc.track_list[c].artist)) id3.add(TALB(encoding=3, text=disc.title)) id3.add(TCOM(encoding=3, text=disc.artist)) id3.add(TPUB(encoding=3, text=disc.label)) id3.add(TDRC(encoding=3, text=disc.year)) id3.add(TXXX(encoding=3, desc='Catalog #', text=disc.cat_num)) id3.add(TCON(encoding=3, text=disc.genre)) id3.add(TRCK(encoding=3, text=str(disc.track_list[c][0]) + "/" + tot)) id3.pprint() id3.save(f,0) elif ft == u'flac': disc.track_list[c].title audio["TITLE"] = disc.track_list[c].title audio["ARTIST"] = disc.track_list[c].artist.artistString audio["ALBUM"] = disc.title audio["COMPOSER"] = disc.artist.artistString audio["ORGANIZATION"] = disc.label audio["CATALOGNUM"] = disc.cat_num audio["GENRE"] = disc.genre if(len(disc.style) != 0): audio["STYLE"] = disc.style audio["YEAR"] = disc.year audio["DATE"] = disc.date audio["TRACKNUMBER"] = str(disc.track_list[c].position) audio["TRACKTOTAL"] = tot
def split_and_send_audio_file(self, bot, chat_id, reply_to_message_id=None, file=""): sent_audio_ids = [] file_root, file_ext = os.path.splitext(file) file_name = os.path.split(file)[-1] file_format = file_ext.replace(".", "") if not (file_format == "mp3" or file_format == "m4a" or file_format == "mp4"): logger.warning("Unsupported file format: %s", file_name) bot.send_message( chat_id=chat_id, reply_to_message_id=reply_to_message_id, text= "*Sorry*, downloaded file `{}` is in format I could not yet send or convert" .format(file_name), parse_mode='Markdown') return "format" file_size = os.path.getsize(file) if file_size > self.MAX_CONVERT_FILE_SIZE: logger.warning("Large file for convert: %s", file_name) bot.send_message( chat_id=chat_id, reply_to_message_id=reply_to_message_id, text= "*Sorry*, downloaded file `{}` is larger than I could convert". format(file_name), parse_mode='Markdown') return "size" parts_number = 1 file_parts = [] if file_size <= self.MAX_TG_FILE_SIZE: file_parts.append(file) else: logger.info("Splitting: %s", file_name) try: id3 = mutagen.id3.ID3(file, translate=False) except: id3 = None parts_number = file_size // self.MAX_TG_FILE_SIZE + 1 try: sound = AudioSegment.from_file(file, file_format) part_size = len(sound) / parts_number for i in range(parts_number): file_part = file.replace(file_ext, ".part" + str(i + 1) + file_ext) part = sound[part_size * i:part_size * (i + 1)] part.export(file_part, format="mp3") del part if id3: id3.save(file_part, v1=2, v2_version=4) file_parts.append(file_part) # https://github.com/jiaaro/pydub/issues/135 # https://github.com/jiaaro/pydub/issues/89#issuecomment-75245610 del sound gc.collect() except (OSError, MemoryError) as exc: text = "Failed pydub convert" logger.exception(text) self.send_alert(bot, text + "\n" + str(exc), file_name) bot.send_message( chat_id=chat_id, reply_to_message_id=reply_to_message_id, text= "*Sorry*, not enough memory to convert file `{}`, you may try again later.." .format(file_name), parse_mode='Markdown') gc.collect() return "memory" file_parts_copy = list(file_parts) for index, file in enumerate(file_parts): logger.info("Sending: %s", file_name) bot.send_chat_action(chat_id=chat_id, action=ChatAction.UPLOAD_AUDIO) # file = translit(file, 'ru', reversed=True) caption = "" if file_size > self.MAX_TG_FILE_SIZE: caption += "\n" + " ".join( ["Part", str(index + 1), "of", str(parts_number)]) for i in range(3): try: if (i == 0) and (chat_id not in self.NO_CLUTTER_CHAT_IDS): caption = "Downloaded with @{}".format( self.bot_username) + caption audio_msg = bot.send_audio( chat_id=chat_id, reply_to_message_id=reply_to_message_id, audio=open(file, 'rb'), caption=caption) sent_audio_ids.append(audio_msg.audio.file_id) file_parts_copy.remove(file) logger.info("Sending success: %s", file_name) break except TelegramError as exc: logger.exception( "Sending failed because of TelegramError: %s", file_name) self.send_alert(bot, "TelegramError:\n" + str(exc), file_name) if not file_parts_copy: return "success" else: logger.warning("Sending total failed: %s", file_name) return "send"
if value.encoding==0: if isinstance(value.text, list): try: bytes= '\n'.join(value.text).encode('iso-8859-1') except TypeError as e: if 'expected str instance, ID3TimeStamp found' in str(e): # TODO: too many different kinds of info avail inside value.text # What are they? How to treat them good? print('ID3TimeStamp found. Skipping ', value.text) continue else: bytes= value.text.encode('iso-8859-1') for encoding in tryencodings: try: bytes.decode(encoding) print(f'Succeed with {encoding}') except UnicodeError: pass else: break else: raise ValueError( 'None of the tryencodings work for %r key %r' % (path, key)) if isinstance(value.text, list): for i in range(len(value.text)): value.text[i]= value.text[i].encode('iso-8859-1').decode(encoding) else: value.text = value.text.encode('iso-8859-1').decode(encoding) value.encoding= 3 id3.save() print(id3)
id3.loaded_frame(frame) trackgain = float(rg["REPLAYGAIN_TRACK_GAIN"][0].split(" ")[0]) if "REPLAYGAIN_TRACK_PEAK" in rg: frame = mutagen.id3.Frames["TXXX"]( encoding=3, desc="replaygain_track_peak", text=rg["REPLAYGAIN_TRACK_PEAK"][0] ) id3.loaded_frame(frame) trackpeak = float(rg["REPLAYGAIN_TRACK_PEAK"][0]) frame = mutagen.id3.Frames["RVA2"](desc="track", channel=1, gain=trackgain, peak=trackpeak) id3.loaded_frame(frame) else: frame = mutagen.id3.Frames["RVA2"](desc="track", channel=1, gain=trackgain) id3.loaded_frame(frame) try: id3.save(file) except Exception, err: print str(err) return 0 return 1 def removeape(file): mutagen.apev2.delete(file) from mutagen.id3 import ID3, COMM, RVA2 def gain_to_watts(gain): return pow(10, -gain * 0.1)
def write_files(edits, filenames, escape): # unescape escape sequences and decode values encoded_edits = [] for frame, value in edits: if not value: continue try: frame = frame_from_fsnative(frame) except ValueError as err: print_(text_type(err), file=sys.stderr) assert isinstance(frame, str) # strip "--" frame = frame[2:] try: value = value_from_fsnative(value, escape) except ValueError as err: error(u"%s: %s" % (frame, text_type(err))) assert isinstance(value, text_type) encoded_edits.append((frame, value)) edits = encoded_edits # preprocess: # for all [frame,value] pairs in the edits list # gather values for identical frames into a list tmp = {} for frame, value in edits: if frame in tmp: tmp[frame].append(value) else: tmp[frame] = [value] # edits is now a dictionary of frame -> [list of values] edits = tmp # escape also enables escaping of the split separator if escape: string_split = split_escape else: string_split = lambda s, *args, **kwargs: s.split(*args, **kwargs) for filename in filenames: with _sig.block(): if verbose: print_(u"Writing", filename, file=sys.stderr) try: id3 = mutagen.id3.ID3(filename) except mutagen.id3.ID3NoHeaderError: if verbose: print_(u"No ID3 header found; creating a new tag", file=sys.stderr) id3 = mutagen.id3.ID3() except Exception as err: print_(str(err), file=sys.stderr) continue for (frame, vlist) in edits.items(): if frame == "POPM": for value in vlist: values = string_split(value, ":") if len(values) == 1: email, rating, count = values[0], 0, 0 elif len(values) == 2: email, rating, count = values[0], values[1], 0 else: email, rating, count = values frame = mutagen.id3.POPM(email=email, rating=int(rating), count=int(count)) id3.add(frame) elif frame == "APIC": for value in vlist: values = string_split(value, ":") # FIXME: doesn't support filenames with an invalid # encoding since we have already decoded at that point fn = values[0] if len(values) >= 2: desc = values[1] else: desc = u"cover" if len(values) >= 3: try: picture_type = int(values[2]) except ValueError: error(u"Invalid picture type: %r" % values[1]) else: picture_type = PictureType.COVER_FRONT if len(values) >= 4: mime = values[3] else: mime = mimetypes.guess_type(fn)[0] or "image/jpeg" if len(values) >= 5: error("APIC: Invalid format") encoding = get_frame_encoding(frame, desc) try: with open(fn, "rb") as h: data = h.read() except IOError as e: error(text_type(e)) frame = mutagen.id3.APIC(encoding=encoding, mime=mime, desc=desc, type=picture_type, data=data) id3.add(frame) elif frame == "COMM": for value in vlist: values = string_split(value, ":") if len(values) == 1: value, desc, lang = values[0], "", "eng" elif len(values) == 2: desc, value, lang = values[0], values[1], "eng" else: value = ":".join(values[1:-1]) desc, lang = values[0], values[-1] frame = mutagen.id3.COMM(encoding=3, text=value, lang=lang, desc=desc) id3.add(frame) elif frame == "UFID": for value in vlist: values = string_split(value, ":") if len(values) != 2: error(u"Invalid value: %r" % values) owner = values[0] data = values[1].encode("utf-8") frame = mutagen.id3.UFID(owner=owner, data=data) id3.add(frame) elif frame == "TXXX": for value in vlist: values = string_split(value, ":", 1) if len(values) == 1: desc, value = "", values[0] else: desc, value = values[0], values[1] frame = mutagen.id3.TXXX(encoding=3, text=value, desc=desc) id3.add(frame) elif issubclass(mutagen.id3.Frames[frame], mutagen.id3.UrlFrame): frame = mutagen.id3.Frames[frame](encoding=3, url=vlist) id3.add(frame) else: frame = mutagen.id3.Frames[frame](encoding=3, text=vlist) id3.add(frame) id3.save(filename)
def _download \ ( self, song_id, metadata = None, thumbnail = None, directory = None, video = False, ): ''' ''' import mutagen.easyid3 import mutagen.id3 import mutagen.mp4 import mutagen.easymp4 import youtube_dl file_name_format = '%(title)s.%(ext)s' if not metadata: metadata = {} if directory: path_directory = pathlib.Path(directory) else: path_directory = pathlib.Path(os.getcwd()) path_file = path_directory.joinpath(file_name_format) post_processors = [] if video: format = 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]' to_ext = 'mp4' else: format = 'bestaudio' to_ext = 'mp3' post_processors.append \ ( { 'key': 'FFmpegExtractAudio', 'preferredcodec': to_ext, } ) ytdl = youtube_dl.YoutubeDL \ ( params = \ { 'quiet': True, 'outtmpl': str(path_file), 'postprocessors': post_processors, 'format': format, } ) url = utils.url_yt \ ( 'watch', params = {'v': song_id}, ) info = ytdl.extract_info \ ( url = url, ie_key = 'Youtube', download = True, ) any_title = info.get('track', info.get('title')) metadata = utils.filter \ ( { 'title': any_title, 'artist': info.get('artist'), 'album': info.get('album'), 'albumartist': info.get('artist'), 'discnumber': '1', 'tracknumber': '1', 'date': str(info.get('release_year')), **metadata, } ) info.setdefault('track', any_title) file_path_src = self._get_file_path \ ( info, file_name_format % \ { 'title': info.get('title'), 'ext': to_ext, }, directory, ) file_path_dst = file_path_src.parent.joinpath \ ( file_name_format % \ { 'title': info.get('title'), 'ext': to_ext, } ) if not thumbnail: thumbnail = self._get_album_art(info['thumbnails'][0]['url'], crop=True) if video: mp4 = mutagen.mp4.MP4(file_path_dst) cover = mutagen.mp4.MP4Cover \ ( thumbnail, imageformat = mutagen.mp4.AtomDataType.PNG, ) mp4['covr'] = (cover,) mp4.save() easymp4 = mutagen.easymp4.EasyMP4(file_path_dst) easymp4.update(metadata) easymp4.save() else: id3 = mutagen.id3.ID3(file_path_dst) ENCODING_UTF8 = 3 MIME_PNG = 'image/png' IMAGE_TYPE_COVER = 3 IMAGE_DESCRIPTION_COVER = 'Cover' id3.add \ ( mutagen.id3.APIC ( encoding = ENCODING_UTF8, mime = MIME_PNG, type = IMAGE_TYPE_COVER, desc = IMAGE_DESCRIPTION_COVER, data = thumbnail, ) ) id3.save(v2_version = 3) easyid3 = mutagen.easyid3.EasyID3(file_path_dst) easyid3.update(metadata) easyid3.save(v2_version = 3) # Rename to {track}.{ext} here? success = not ytdl._download_retcode if success: return info
def write_files(edits, filenames, escape): # unescape escape sequences and decode values encoded_edits = [] for frame, value in edits: if not value: continue try: frame = frame_from_fsnative(frame) except ValueError as err: print_(text_type(err), file=sys.stderr) assert isinstance(frame, str) # strip "--" frame = frame[2:] try: value = value_from_fsnative(value, escape) except ValueError as err: error(u"%s: %s" % (frame, text_type(err))) assert isinstance(value, text_type) encoded_edits.append((frame, value)) edits = encoded_edits # preprocess: # for all [frame,value] pairs in the edits list # gather values for identical frames into a list tmp = {} for frame, value in edits: if frame in tmp: tmp[frame].append(value) else: tmp[frame] = [value] # edits is now a dictionary of frame -> [list of values] edits = tmp # escape also enables escaping of the split separator if escape: string_split = split_escape else: string_split = lambda s, *args, **kwargs: s.split(*args, **kwargs) for filename in filenames: with _sig.block(): if verbose: print_(u"Writing", filename, file=sys.stderr) try: id3 = mutagen.id3.ID3(filename) except mutagen.id3.ID3NoHeaderError: if verbose: print_(u"No ID3 header found; creating a new tag", file=sys.stderr) id3 = mutagen.id3.ID3() except Exception as err: print_(str(err), file=sys.stderr) continue for (frame, vlist) in edits.items(): if frame == "POPM": for value in vlist: values = string_split(value, ":") if len(values) == 1: email, rating, count = values[0], 0, 0 elif len(values) == 2: email, rating, count = values[0], values[1], 0 else: email, rating, count = values frame = mutagen.id3.POPM( email=email, rating=int(rating), count=int(count)) id3.add(frame) elif frame == "APIC": for value in vlist: values = string_split(value, ":") # FIXME: doesn't support filenames with an invalid # encoding since we have already decoded at that point fn = values[0] if len(values) >= 2: desc = values[1] else: desc = u"cover" if len(values) >= 3: try: picture_type = int(values[2]) except ValueError: error(u"Invalid picture type: %r" % values[1]) else: picture_type = PictureType.COVER_FRONT if len(values) >= 4: mime = values[3] else: mime = mimetypes.guess_type(fn)[0] or "image/jpeg" if len(values) >= 5: error("APIC: Invalid format") encoding = get_frame_encoding(frame, desc) try: with open(fn, "rb") as h: data = h.read() except IOError as e: error(text_type(e)) frame = mutagen.id3.APIC(encoding=encoding, mime=mime, desc=desc, type=picture_type, data=data) id3.add(frame) elif frame == "COMM": for value in vlist: values = string_split(value, ":") if len(values) == 1: value, desc, lang = values[0], "", "eng" elif len(values) == 2: desc, value, lang = values[0], values[1], "eng" else: value = ":".join(values[1:-1]) desc, lang = values[0], values[-1] frame = mutagen.id3.COMM( encoding=3, text=value, lang=lang, desc=desc) id3.add(frame) elif frame == "USLT": for value in vlist: values = string_split(value, ":") if len(values) == 1: value, desc, lang = values[0], "", "eng" elif len(values) == 2: desc, value, lang = values[0], values[1], "eng" else: value = ":".join(values[1:-1]) desc, lang = values[0], values[-1] frame = mutagen.id3.USLT( encoding=3, text=value, lang=lang, desc=desc) id3.add(frame) elif frame == "UFID": for value in vlist: values = string_split(value, ":") if len(values) != 2: error(u"Invalid value: %r" % values) owner = values[0] data = values[1].encode("utf-8") frame = mutagen.id3.UFID(owner=owner, data=data) id3.add(frame) elif frame == "TXXX": for value in vlist: values = string_split(value, ":", 1) if len(values) == 1: desc, value = "", values[0] else: desc, value = values[0], values[1] frame = mutagen.id3.TXXX( encoding=3, text=value, desc=desc) id3.add(frame) elif frame == "WXXX": for value in vlist: values = string_split(value, ":", 1) if len(values) == 1: desc, value = "", values[0] else: desc, value = values[0], values[1] frame = mutagen.id3.WXXX( encoding=3, url=value, desc=desc) id3.add(frame) elif issubclass(mutagen.id3.Frames[frame], mutagen.id3.UrlFrame): frame = mutagen.id3.Frames[frame]( encoding=3, url=vlist[-1]) id3.add(frame) else: frame = mutagen.id3.Frames[frame](encoding=3, text=vlist) id3.add(frame) id3.save(filename)